Нарезка pandas кадра данных по пользовательским месяцам и дням - есть ли способ избежать циклов? - PullRequest
1 голос
/ 10 марта 2020

Проблема

Предположим, у меня есть временной ряд данных df (pandas dataframe), и в некоторые дни я хочу выделить его, содержащийся в другом кадре данных с именем sample_days:

>>> df

                          foo       bar
2020-01-01 00:00:00  0.360049  0.897839
2020-01-01 01:00:00  0.285667  0.409544
2020-01-01 02:00:00  0.323871  0.240926
2020-01-01 03:00:00  0.921623  0.766624
2020-01-01 04:00:00  0.087618  0.142409
...                       ...       ...
2020-12-31 19:00:00  0.145111  0.993822
2020-12-31 20:00:00  0.331223  0.021287
2020-12-31 21:00:00  0.531099  0.859035
2020-12-31 22:00:00  0.759594  0.790265
2020-12-31 23:00:00  0.103651  0.074029

[8784 rows x 2 columns]
>>> sample_days

   month  day
0      3   16
1      7   26
2      8   15
3      9   26
4     11   25

Я хочу нарезать df с днями, указанными в sample_days. Я могу сделать это с помощью циклов (см. Ниже). Тем не менее, есть ли способ избежать циклов (как это более эффективно)? Результатом должен быть фрейм данных с именем sample, подобный следующему:

>>> sample

                          foo       bar
2020-03-16 00:00:00  0.707276  0.592614
2020-03-16 01:00:00  0.136679  0.357872
2020-03-16 02:00:00  0.612331  0.290126
2020-03-16 03:00:00  0.276389  0.576996
2020-03-16 04:00:00  0.612977  0.781527
...                       ...       ...
2020-11-25 19:00:00  0.904266  0.825501
2020-11-25 20:00:00  0.269589  0.050304
2020-11-25 21:00:00  0.271814  0.418235
2020-11-25 22:00:00  0.595005  0.973198
2020-11-25 23:00:00  0.151149  0.024057

[120 rows x 2 columns

, который представляет собой просто df, разрезанный на правильные дни.

Мое (медленное) решение

Мне удалось сделать это, используя циклы for и pd.concat:

sample = pd.concat([df.loc[df.index.month.isin([sample_day.month]) &
                           df.index.day.isin([sample_day.day])] 
                    for sample_day in sample_days.itertuples()])

, основанные на конкатенации нескольких дней в разрезе по методу, указанному здесь . Это дает желаемый результат, но довольно медленно. Например, использование этого метода для получения первого дня каждого месяца занимает в среднем 0,2 секунды, тогда как простой вызов df.loc[df.index.day == 1] (предположительно, избегая python для циклов под колпаком) происходит примерно в 300 раз быстрее. Тем не менее, это срез только на день - я нарезаю на месяц и день.

Извините, если на этот вопрос ответили где-то еще - я долго искал, но, возможно, не использовал правильные ключевые слова.

Ответы [ 2 ]

0 голосов
/ 11 марта 2020

Получив некоторое вдохновение от решения @Ben Pap (спасибо!), Я нашел решение, которое является одновременно быстрым и позволяет избежать любых «взломов», таких как изменение даты и времени на строки. Он объединяет месяц и день в один MultiIndex, как показано ниже (вы можете сделать это одной строкой, но я расширил ее до нескольких, чтобы прояснить идею).

full_index = pd.MultiIndex.from_arrays([df.index.month, df.index.day],
                                       names=['month', 'day'])
sample_index = pd.MultiIndex.from_frame(sample_days)
sample = df.loc[full_index.isin(sample_index)]

Если я запустлю это Код вместе с моим оригиналом для l oop и ответом @Ben Pap, и выборкой 100 дней из временного ряда одного года для 2020 года (8784 часа с високосным днем), я получаю следующие времена решения:

  • Оригинал для l oop: 0,16 с
  • @ решение Бен Папа, объединяющее месяц и день в одну строку: 0,019 с
  • Над решением с использованием MultiIndex: 0,006 с

поэтому я думаю, что использование MultiIndex - это путь к go.

0 голосов
/ 10 марта 2020

Вы можете сделать сравнение строк месяца и дней одновременно.

Вам нужно пробел, чтобы различать, например, 11 2 и 1 12, в противном случае оба будут считаться одинаковыми .

df.loc[(df.index.month.astype(str) +' '+ df.index.day.astype(str)).isin(sample_days['month'].astype(str)+' '+sample_days['day'].astype(str))]
...