2016-05-18 2 views
1

Это проблема преобразования диапазона дат в числовые значения на основе текущей даты.векторизация функции диапазона дат для преобразования pandas dataframe

Входной стол:

ID START_DATE END_DATE CURRENT_DATE 
    1 2010-12-08 2011-03-01 2011-04-01 
    2 2010-12-10 2011-01-12 2011-01-02 
    3 2010-12-16 2011-03-07 2010-10-10 

Выходной стол:

ID START_DATE END_DATE CURRENT_DATE number_of_days 
    1 2010-12-08 2011-03-01 2011-04-01  78.148490 
    2 2010-12-10 2011-01-12 2011-01-02  23.726149 
    3 2010-12-16 2011-03-07 2010-10-10  0.000000 

, где nubmer_of_days вычисляется на основе экспоненциальной функции распада, а затем путем суммирования всех значений для одной строки.

Мы можем реализовать функцию следующим образом:

def transform(start, end, current): 
    value = 0 
    if current > end: #current date is later than the end date 
     delta = end - start 
     for i in range(delta.days + 1): 
      diff = current - (start + td(days = i)) 
      value += math.exp(- 0.001 * diff.days) 
    elif current > start: #current date is between the start and end 
     delta = current - start 
     for i in range(delta.days + 1): 
      diff = current - (start + td(days = i)) 
      value += math.exp(-0.001 * diff.days) 
    else: 
     pass 
    return value 

, а затем применить ниже преобразование:

df['number_of_days'] = df.apply(lambda x: transform(x['START_DATE'], x['END_DATE'], x['CURRENT_DATE']),axis=1) 

Однако это очень медленный для таблицы с миллионами строк и огромный диапазон дат ,

Любая идея о том, как ускорить процесс путем векторизации внутреннего цикла цикла в функции преобразования?

Спасибо!

ответ

1

Вы можете в векторном формате с помощью numpy array функций для вычисления экспоненциального распада.

df = df[df.CURRENT_DATE > df.START_DATE] # just focusing on cases with calculation 

Получить соответствующие delta в зависимости от CURRENT_DATE и END_DATE:

delta = df[['END_DATE', 'CURRENT_DATE']].min(axis=1).subtract(df.START_DATE).dt.days.add(1) 

Вычислить shift в arange() для экспоненциального распада, max разницы между END_DATE и CURRENT_DATE или 0:

shift = df.CURRENT_DATE.subtract(df.END_DATE).dt.days.clip(lower=0) 

Производство и обработки (регулируется) arange объектов с помощью np.exp() и np.sum():

df['number_of_days'] = [np.sum(np.exp(-0.001 * (np.arange(d) + s))) for d, s in zip(delta.values, shift.values)] 

, чтобы получить:

START_DATE END_DATE CURRENT_DATE number_of_days 
ID             
1 2010-12-08 2011-03-01 2011-04-01  78.148490 
2 2010-12-10 2011-01-12 2011-01-02  23.726149 

Если сравнить производительность, вы видите повышение эффективности от экономии на петлях:

df_test = pd.concat([df for _ in range(100000)]) 

def transform1(df): 
    df = df[df.CURRENT_DATE > df.START_DATE] 
    delta = df[['END_DATE', 'CURRENT_DATE']].min(axis=1).subtract(df.START_DATE).dt.days.add(1) 
    shift = df.CURRENT_DATE.subtract(df.END_DATE).dt.days.clip(lower=0) 
    return [np.sum(np.exp(-0.001 * (np.arange(d) + s))) for d, s in zip(delta.values, shift.values)] 

%timeit transform1(df_test) 
1 loop, best of 3: 4.99 s per loop 

def transform2(df): 
    df['end'] = [d.days for d in df.CURRENT_DATE - df.START_DATE] 
    df['start'] = (df.end - [max(0, d.days + 1) for d in (df.END_DATE.where(df.CURRENT_DATE > df.END_DATE, df.CURRENT_DATE) - df.START_DATE)]) 
    df['number_of_days'] = [sum(np.exp(-0.001 * i) for i in np.arange(stop, start, -1)) for start, stop in zip(df.start, df.end)] 
    df.drop(['start', 'end'], axis=1, inplace=True) 

%timeit transform2(df_test) 
1 loop, best of 3: 36.7 s per loop 
+0

спасибо. Это быстро! – Yiliang

1

Вы хотите получить начало и конец (целые числа) для каждого диапазона дат. Тогда относительно легко векторизовать расчет number_of_days.

df['end'] = [d.days for d in df.CURRENT_DATE - df.START_DATE] 
df['start'] = (
    df.end - [max(0, d.days + 1) 
       for d in (df.END_DATE.where(df.CURRENT_DATE > df.END_DATE, df.CURRENT_DATE) 
         - df.START_DATE)]) 

df['number_of_days'] = [sum(np.exp(-0.001 * i) for i in np.arange(stop, start, -1)) 
         for start, stop in zip(df.start, df.end)] 
df.drop(['start', 'end'], axis=1, inplace=True) 

>>> df 
    ID START_DATE END_DATE CURRENT_DATE number_of_days 
0 1 2010-12-08 2011-03-01 2011-04-01  78.148490 
1 2 2010-12-10 2011-01-12 2011-01-02  23.726149 
2 3 2010-12-16 2011-03-07 2010-10-10  0.000000 
+0

Спасибо! Время работы значительно улучшилось. – Yiliang

Смежные вопросы