2017-09-14 196 views
0

我有一個Pandas DataFrame訂閱,每個訂閱都有一個開始日期時間(時間戳)和一個可選的結束日期時間(如果它們被取消)。使用日期範圍創建一個來自DataFrame的Pandas每日總計時間系列

爲了簡單起見,我基於開始和結束日期時間(時間戳)爲日期創建了字符串列(例如「20170901」)。它看起來像這樣:

df = pd.DataFrame([('20170511', None), ('20170514', '20170613'), ('20170901', None), ...], columns=["sd", "ed"])

最終的結果應該是一個時間序列的許多訂戶是如何活躍在範圍內的任何給定的日期。

爲此,我創建了一個指數的所有天的範圍內:

days = df.groupby(["sd"])["sd"].count()

我能創造什麼,我有一個循環,在整個數據幀的每個執行查詢興趣df

count_by_day = pd.DataFrame([ len(df.loc[(df.sd <= i) & (df.ed.isnull() | (df.ed > i))]) for i in days.index], index=days.index)

注意,我每天在原始數據集的值,因此不存在任何差距。我確定日期範圍可以改進。

實際的問題是:是否有一種有效的方法來計算這個大型初始數據集df,具有數千行?看來我使用的方法在複雜度上是二次的。我也試過df.query(),但它比Pythonic濾波器慢了66%,並沒有改變複雜性。

我試圖搜索熊貓文檔的例子,但我似乎使用了錯誤的關鍵字。有任何想法嗎?

回答

1

這是一個有趣的問題,這裏是我該怎麼做。不知道性能

編輯:我的第一個答案是不正確的,我沒有讀充分的問題

# Initial data, columns as Timestamps 
df = pd.DataFrame([('20170511', None), ('20170514', '20170613'), ('20170901', None)], columns=["sd", "ed"]) 
df['sd'] = pd.DatetimeIndex(df.sd) 
df['ed'] = pd.DatetimeIndex(df.ed) 

# Range input and related index 
beg = pd.Timestamp('2017-05-15') 
end = pd.Timestamp('2017-09-15') 
idx = pd.DatetimeIndex(start=beg, end=end, freq='D') 

# We filter data for records out of the range and then clip the 
# the subscriptions start/end to the range bounds. 
fdf = df[(df.sd <= beg) | ((df.ed >= end) | (pd.isnull(df.ed)))] 
fdf['ed'].fillna(end, inplace=True) 
fdf['ps'] = fdf.sd.apply(lambda x: max(x, beg)) 
fdf['pe'] = fdf.ed.apply(lambda x: min(x, end)) 

# We run a conditional count 
idx.to_series().apply(lambda x: len(fdf[(fdf.ps<=x) & (fdf.pe >=x)])) 
+0

謝謝!一些代碼模式比我的問題中的代碼好得多。 – mike921

0

好吧,我回答我的問題相當多的研究後,順藤摸瓜,試圖事情了。我可能仍然缺少一個明顯的解決方案,但也許會有所幫助。

我能找到最新最快的解決方法是(感謝亞歷克斯一些不錯的代碼模式):

# Start with test data from question 
df = pd.DataFrame([('20170511', None), ('20170514', '20170613'), 
        ('20170901', None), ...], columns=['sd', 'ed']) 

# Convert to datetime columns 
df['sd'] = pd.DatetimeIndex(df['sd']) 
df['ed'] = pd.DatetimeIndex(df['ed']) 
df.ed.fillna(df.sd.max(), inplace=True) 

# Note: In my real data I have timestamps - I convert them like this: 
#df['sd'] = pd.to_datetime(df['start_date'], unit='s').apply(lambda x: x.date()) 

# Set and sort multi-index to enable slices 
df = df.set_index(['sd', 'ed'], drop=False) 
df.sort_index(inplace=True) 

# Compute the active counts by day in range 
di = pd.DatetimeIndex(start=df.sd.min(), end=df.sd.max(), freq='D') 
count_by_day = di.to_series().apply(lambda i: len(df.loc[ 
      (slice(None, i.date()), slice(i.date(), None)), :])) 

在我的實際數據集(含> 10K行對df和大約一年的日期範圍) ,這是問題代碼的兩倍,大約1.5s。

下面一些經驗我學到:

  • 創建與計數器的日期範圍內的系列,並通過數據集dfdf.applydf.itertuples迭代和遞增計數器是慢得多。奇怪的是,applyitertuples慢。千萬別想到iterrows
  • 我的數據集的每行都有一個product_id,所以篩選每個產品的數據集並對篩選結果(針對每個產品)運行計算的速度是將product_id添加到多個並且在該級別上也進行切片
  • 構建活動日的中間系列(通過遍歷df中的每一行並將活動範圍中的每個日期添加到系列中),然後按日期分組要慢得多。
  • 使用多索引在df上運行問題中的代碼不會改變性能。
  • df的問題中運行有限的一組列(我的真實數據集有22列)中的代碼不會改變性能。
  • 我看着pd.crosstabpd.Period,但我沒能得到任何工作
  • 大熊貓是相當真棒,並試圖智取真的很難(尤指非矢量化在Python)
相關問題