2016-05-18 6 views
1

これは、日付範囲を現在の日付に基づく数値に変換する問題です。パンダデータフレーム変換の日付範囲関数のベクトル化

入力テーブル:

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 

出力テーブル:

nubmer_of_daysが1行のすべての値の合計が続く指数関数的減衰関数に基づいて計算される
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 

次のように私たちは、機能を実装することができます

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) 

が、これは数百万行、巨大な日付範囲を持つテーブルのための非常に遅いです。

変換関数の内側のforループをベクトル化することによってプロセスをスピードアップする方法については、どんな考えですか?

ありがとうございました!

答えて

1

numpy array関数を使用してベクトル化すると、指数関数的減衰を計算できます。

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

CURRENT_DATEEND_DATEに応じて、関連するdeltaを取得:

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

END_DATECURRENT_DATEまたは0との差のmaxとして指数関数的減衰のためarange()shiftを計算します。

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

プロデュース及び(調整)arangeオブジェクトnp.exp()np.sum()を使用して処理:

:あなたはパフォーマンスを比較した場合、あなたはループの節約から効率の向上を参照してください

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['number_of_days'] = [np.sum(np.exp(-0.001 * (np.arange(d) + s))) for d, s in zip(delta.values, shift.values)] 

を取得することを

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