2015-08-09 4 views
3

У меня есть строки, представляющие диапазоны (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)?

ответ

0

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

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

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 (i.e. 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) 
+0

Это странно, в моем маленьком тесте с набором данных из 7361 строк, я получить следующие результаты: - Начальное решение 9,31 сек - Johne решение 3.123 сек - Последнее решение 0,431 сек - HYRY решения 0,077 s I» м, несовместимый с раствором HYRY. Огромное спасибо за всю вашу работу по этому вопросу. Мне было очень интересно изучить эти ответы. – Romain

+0

Хмм, эти результаты мне удивительны, но я только приурочил образец данных, предоставленный в вопросе, который может не обобщать на больший (и другой) фрейм данных, поэтому я удалил свой предыдущий комментарий. Спасибо за продолжение. – JohnE

2

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

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 (аналог панд value_counts), чтобы добавить все. Я надеялся придумать что-то более элегантное и читаемое, но это то, что у меня есть на данный момент.

+0

Танки, но он не смог вычислить результат с реальным случаем, считая 6361 строк. Моя iPython ноутбука замерзает. – Romain

+0

Извините, это моя вина, и это не связано с преобразованием даты. Я работал с гораздо большим «DataFrame», содержащим другие столбцы. В этом контексте было неплохо использовать функцию 'apply'. После этой коррекции он работает нормально, и я получаю тот же результат за меньшее время. Спасибо за решение и за ответ. – Romain

+0

Несомненно, никаких проблем. Как я признал, это не самое красивое решение, но оно должно быть достаточно быстрым и надежным. Спасибо за продолжение. – JohnE

2

Это метод, который использует 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) 
+0

Очень быстрое и чистое решение, оно работает хорошо, я буду использовать его. Благодарю. – Romain

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