Создайте столбцы 0–1 на основе другого фрейма данных с датами начала и окончания событий

Контекст: у меня есть фрейм данных с индексом времени и связанными значениями. И еще один фрейм данных («фрейм данных событий»), в котором каждая строка представляет собой событие со временем начала и окончания и типом.

Вопрос: какой самый простой и самый быстрый/эффективный способ создать новый столбец в моем фрейме данных для каждой строки в моем фрейме данных событий.

Вот некоторые примеры данных и мой текущий подход.

import pandas as pd
import numpy as np

np.random.seed(0)
df_idx = pd.date_range(start='2024-03-04', end='2024-03-10', freq='h')
df = pd.DataFrame(data = {'placeholder': np.random.randint(1000,size=len(df_idx))}, index=df_idx)

df_events = pd.DataFrame([
    ['football', pd.Timestamp('2024-03-04 15:00:00'), pd.Timestamp('2024-03-04 16:59:59')]
    ,['concert', pd.Timestamp('2024-03-05 19:00:00'), pd.Timestamp('2024-03-05 21:59:59')]
    , ['football', pd.Timestamp('2024-03-07 18:00:00'), pd.Timestamp('2024-03-07 19:59:59')]
    , ['football', pd.Timestamp('2024-03-08 18:00:00'), pd.Timestamp('2024-03-08 19:59:59')]
    , ['public_event', pd.Timestamp('2024-03-09 16:00:00'), pd.Timestamp('2024-03-09 23:59:59')]
]
    , columns=['type','start','end']
            )

#This is my current approach

#Get the unique events types
events = df_events['type'].unique()

#Iterate over the df_events rows per event, and create a new column in df
for event in events:
    df[event]=0
    for idx, row in df_events[df_events['type']==event].iterrows():
        df.loc[(df.index>=row['start'])&(df.index<=row['end']),event]=1

df.head(5)

Ожидаемым результатом будет фрейм данных с исходным индексом времени и столбцом-заполнителем, а также 3 других столбца, называемых «футбол», «концерт» и публичные мероприятия с 0 и 1, где индекс времени находится между началом и концом соответствующие события.

Что-то вроде этого:

Создайте столбцы 0–1 на основе другого фрейма данных с датами начала и окончания событий

Есть ли лучший способ сделать это?

Примечание: конечно, мои фреймы данных на самом деле намного больше, и я получаю предупреждение из-за подхода iterrows.

Вы не против добавить ожидаемый результат?

rpanai 23.08.2024 18:33

пожалуйста, используйте воспроизводимый набор данных. Я добавил np.random.seed(0) в ваш набор данных, чтобы всегда обеспечивать одни и те же данные.

sammywemmy 24.08.2024 02:09
Почему в Python есть оператор "pass"?
Почему в Python есть оператор "pass"?
Оператор pass в Python - это простая концепция, которую могут быстро освоить даже новички без опыта программирования.
Некоторые методы, о которых вы не знали, что они существуют в Python
Некоторые методы, о которых вы не знали, что они существуют в Python
Python - самый известный и самый простой в изучении язык в наши дни. Имея широкий спектр применения в области машинного обучения, Data Science,...
Основы Python Часть I
Основы Python Часть I
Вы когда-нибудь задумывались, почему в программах на Python вы видите приведенный ниже код?
LeetCode - 1579. Удаление максимального числа ребер для сохранения полной проходимости графа
LeetCode - 1579. Удаление максимального числа ребер для сохранения полной проходимости графа
Алиса и Боб имеют неориентированный граф из n узлов и трех типов ребер:
Оптимизация кода с помощью тернарного оператора Python
Оптимизация кода с помощью тернарного оператора Python
И последнее, что мы хотели бы показать вам, прежде чем двигаться дальше, это
Советы по эффективной веб-разработке с помощью Python
Советы по эффективной веб-разработке с помощью Python
Как веб-разработчик, Python может стать мощным инструментом для создания эффективных и масштабируемых веб-приложений.
2
2
89
3
Перейти к ответу Данный вопрос помечен как решенный

Ответы 3

Ответ принят как подходящий

Вы можете использовать это:

ranges = pd.Series(
    [
        pd.date_range(start, end, freq = "H")
        for start, end in zip(df_events["start"], df_events["end"])
    ]
)

types = (
    ranges.explode()
    .to_frame(name = "range")
    .join(df_events["type"])
    .set_index("range")["type"]
)

df = (
    df.join(pd.get_dummies(types, prefix = "", prefix_sep = ""))
    .fillna(0)
    .astype(int)
    .groupby(level=0)
    .max()
)
                     placeholder  concert  football  public_event
2024-03-04 00:00:00          684        0         0             0
2024-03-04 01:00:00          559        0         0             0
2024-03-04 02:00:00          629        0         0             0
2024-03-04 03:00:00          192        0         0             0
2024-03-04 04:00:00          835        0         0             0
...                          ...      ...       ...           ...
2024-03-09 20:00:00          554        0         0             1
2024-03-09 21:00:00          973        0         0             1
2024-03-09 22:00:00          368        0         0             1
2024-03-09 23:00:00          999        0         0             1
2024-03-10 00:00:00          917        0         0             0

[145 rows x 4 columns]

print(df[df["concert"].eq(1) | df["football"].eq(1) | df["public_event"].eq(1)]) выведет:

                     placeholder  concert  football  public_event
2024-03-04 15:00:00          472        0         1             0
2024-03-04 16:00:00          600        0         1             0
2024-03-05 19:00:00          755        1         0             0
2024-03-05 20:00:00          797        1         0             0
2024-03-05 21:00:00          659        1         0             0
2024-03-07 18:00:00          779        0         1             0
2024-03-07 19:00:00          430        0         1             0
2024-03-08 18:00:00          660        0         1             0
2024-03-08 19:00:00          227        0         1             0
2024-03-09 16:00:00          816        0         0             1
2024-03-09 17:00:00          861        0         0             1
2024-03-09 18:00:00          387        0         0             1
2024-03-09 19:00:00          610        0         0             1
2024-03-09 20:00:00          554        0         0             1
2024-03-09 21:00:00          973        0         0             1
2024-03-09 22:00:00          368        0         0             1
2024-03-09 23:00:00          999        0         0             1

Вы правы, была опечатка, исправил данные примера

Adn 24.08.2024 12:04

@Я обновил ответ, указав результат, используя исправленные данные образца. Решение остается тем же.

e-motta 24.08.2024 12:39

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

Adn 24.08.2024 16:00

@Adn У тебя есть серьезные опасения. Я обновил свой ответ, добавив другой способ построения диапазонов дат без apply, что должно быть намного быстрее. Кроме того, я добавил groupby в индекс, агрегирующий с max в конце, чтобы справиться с перекрывающимися событиями.

e-motta 24.08.2024 17:29

Я знаю, что образцы данных OP были созданы с помощью freq='h', но знаем ли мы, что можем предположить, что реальные данные имеют регулярно расположенные временные метки, как и образцы данных? Если нет, то этот подход не сработает. Он зависит от того, сможете ли вы точно сопоставить временные метки df_idx с помощью pd.date_range().

Stuart Berg 25.08.2024 05:56

для меня это сработает, потому что в реальных данных я буду использовать resample('h').sum(), чтобы получить монотонный индекс с часовыми интервалами @e-motta. Я не понимаю полезности prefix = "", prefix_sep = ""; Пробовал с и без, результат не изменился

Adn 25.08.2024 21:33

у вас есть соединение диапазона, форма соединения неравенства, когда столбец из одного фрейма данных находится между двумя столбцами из другого фрейма данных. Conditional_join от pyjanitor предлагает эффективный способ выполнить это.

Краткое описание шагов:

  • вычислить условное соединение df и df_events
  • свести полученные данные и использовать столбцы событий в качестве заголовков
  • сделайте левое соединение обратно к df и заполните, где это необходимо
# pip install pyjanitor
import pandas as pd
import janitor

events = df_events['type'].unique()
# conditional_join works only with columns, not index
df = df.reset_index(names='timestamps')
# get rows that match the conditional join
merged = (df_events
         .conditional_join(
             df['timestamps'], 
             ('start', 'timestamps', '<='), 
             ('end', 'timestamps','>='), 
             df_columns='type')
         .assign(ones=1)
        )
# pivot the merged dataframe and reindex the columns
# reindexing ensures all events are accounted for
pivoted = (merged
           .pivot(index='timestamps', columns='type', values='ones')
           .reindex(columns=events)
           )
# do a left join back to the original dataframe
# and fill nulls
(df
.merge(pivoted, on='timestamps', how='left')
.set_index('timestamps')
.fillna(0)
.astype(int)
)

                     placeholder  football  concert  public_event
timestamps
2024-03-04 00:00:00          684         0        0             0
2024-03-04 01:00:00          559         0        0             0
2024-03-04 02:00:00          629         0        0             0
2024-03-04 03:00:00          192         0        0             0
2024-03-04 04:00:00          835         0        0             0
...                          ...       ...      ...           ...
2024-03-09 20:00:00          554         0        0             0
2024-03-09 21:00:00          973         0        0             0
2024-03-09 22:00:00          368         0        0             0
2024-03-09 23:00:00          999         0        0             0
2024-03-10 00:00:00          917         0        0             0

Я думаю, что ваш первоначальный подход близок, но его можно очистить и сделать более эффективным с помощью всего одного цикла for:

import pandas as pd
import numpy as np

np.random.seed(0)
df_idx = pd.date_range(start='2024-03-04', end='2024-03-10', freq='h')
df = pd.DataFrame(data = {'placeholder': np.random.randint(1000,size=len(df_idx))}, index=df_idx)

events = [
    ['football',     '2024-03-04 15:00:00', '2024-03-04 16:59:59'],
    ['concert',      '2024-03-05 19:00:00', '2024-03-05 21:59:59'],
    ['football',     '2024-03-07 18:00:00', '2024-03-07 19:59:59'],
    ['football',     '2024-03-08 18:00:00', '2024-03-08 19:59:59'],
    ['public_event', '2024-03-09 16:00:00', '2024-03-09 23:59:59']
]
df_events = pd.DataFrame(events, columns=['type', 'start', 'end'])
df_events = df_events.astype({'start': 'datetime64[ns]', 'end': 'datetime64[ns]'})

event_types = sorted(df_events['type'].unique())
df[event_types] = 0
for event_type, start, end in df_events.itertuples(index=False):
    df.loc[start:end, event_type] = 1

print(df.loc[df[event_types].any(axis=1)])
                     placeholder  concert  football  public_event
2024-03-04 15:00:00          472        0         1             0
2024-03-04 16:00:00          600        0         1             0
2024-03-05 19:00:00          755        1         0             0
2024-03-05 20:00:00          797        1         0             0
2024-03-05 21:00:00          659        1         0             0
2024-03-07 18:00:00          779        0         1             0
2024-03-07 19:00:00          430        0         1             0
2024-03-08 18:00:00          660        0         1             0
2024-03-08 19:00:00          227        0         1             0
2024-03-09 16:00:00          816        0         0             1
2024-03-09 17:00:00          861        0         0             1
2024-03-09 18:00:00          387        0         0             1
2024-03-09 19:00:00          610        0         0             1
2024-03-09 20:00:00          554        0         0             1
2024-03-09 21:00:00          973        0         0             1
2024-03-09 22:00:00          368        0         0             1
2024-03-09 23:00:00          999        0         0             1

Другие вопросы по теме