Подсчитайте количество дней в нескольких диапазонах

У меня есть строки, представляющие диапазоны ( from -> to ). Вот подмножество данных.

 df = DataFrame({'from': ['2015-08-24','2015-08-24'], 'to': ['2015-08-26','2015-08-31']}) from to 0 2015-08-24 2015-08-26 1 2015-08-24 2015-08-31 

Я хочу подсчитать количество рабочих дней для каждого дня в диапазонах. Вот мой код.

 # Creating a business time index by taking min an max values from the ranges b_range = pd.bdate_range(start=min(df['from']), end=max(df['to'])) # Init of a new DataFrame with this index and the count at 0 result = DataFrame(0, index=b_range, columns=['count']) # Iterating over the range to select the index in the result and update the count column for index, row in df.iterrows(): result.loc[pd.bdate_range(row['from'],row['to']),'count'] += 1 print(result) count 2015-08-24 2 2015-08-25 2 2015-08-26 2 2015-08-27 1 2015-08-28 1 2015-08-31 1 

Он работает, но знает ли кто-нибудь более питонический способ сделать это (т. Е. Без цикла for )?

Предостережение, я вроде ненавижу этот ответ, но на этом крошечном фреймворке он больше, чем на 2x быстрее, поэтому я выброшу его как работоспособную, если не изящную альтернативу.

 df2 = df.apply( lambda x: [ pd.bdate_range( x['from'], x['to'] ) ], axis=1 ) arr = np.unique( np.hstack( df2.values ), return_counts=True ) result = pd.DataFrame( arr[1], index=arr[0] ) 

В основном все, что я делаю здесь, это сделать столбец со всеми датами в нем, а затем использовать numpy unique (analog of pandas value_counts ), чтобы добавить все. Я надеялся придумать что-то более элегантное и читаемое, но это то, что у меня есть на данный момент.

Вот метод, который использует cumsum() . Он должен быть быстрее, чем для цикла, если у вас есть много диапазона:

 import pandas as pd df = pd.DataFrame({ 'from': ['2015-08-24','2015-08-24'], 'to': ['2015-08-26','2015-08-31']}) df = df.apply(pd.to_datetime) from_date = min(df['from']) to_date = max(df['to']) b_range = pd.bdate_range(start=from_date, end=to_date) d_range = pd.date_range(start=from_date, end=to_date) s = pd.Series(0, index=d_range) from_count = df["from"].value_counts() to_count = df["to"].value_counts() s.add(from_count, fill_value=0).sub(to_count.shift(freq="D"), fill_value=0).cumsum().reindex(b_range) 

Эти решения не были полностью удовлетворены. Поэтому я продолжал искать, и я думаю, что нашел довольно элегантное и быстрое решение. Он вдохновлен разделом «Поворот» длинного «широкого» формата », объясненного в книге Уэса МакКинни:« Python for Data Analysis » .

Я поместил много комментариев в свой код, но думаю, что лучше всего распечатать каждый шаг, чтобы понять его.

 df = DataFrame({'from': ['2015-08-24','2015-08-24'], 'to': ['2015-08-26','2015-08-31']}) # Convert boundaries to datetime df['from'] = pd.to_datetime(df['from'], format='%Y-%m-%d') df['to'] = pd.to_datetime(df['to'], format='%Y-%m-%d') # Reseting index to create a row id named index df = df.reset_index(level=0) # Pivoting data to obtain 'from' as row index and row id ('index') as column, # each cell cointaining the 'to' date # In consequence each range (from - to pair) is split into as many columns. pivoted = df.pivot('from', 'index', 'to') # Reindexing the table with a range of business dates (ie working days) pivoted = pivoted.reindex(index=pd.bdate_range(start=min(df['from']), end=max(df['to']))) # Filling the NA values forward to copy the to date # now each row of each column contains the corresponding to date pivoted = pivoted.fillna(method='ffill') # Computing the basically 'from' - 'to' for each column and each row and converting the result in days # to obtain the number of days between the date in the index and the 'to' date # Note: one day is added to include the right side of the interval pivoted = pivoted.apply(lambda x: (x + Day() - x.index) / np.timedelta64(1, 'D'), axis=0) # Clipping value lower than 0 (not in the range) to 0 # and values upper than 0 to 1 (only one by day and by id) pivoted = pivoted.clip_lower(0).clip_upper(1) # Summing along the columns and that's it pivoted.sum(axis=1)