Вставка строк в качестве разделителей в данные временных рядов

#python #time-series

Вопрос:

Я работаю над проектом, который анализирует поведение студентов при нажатии в онлайн-курсе, мы рассматриваем путь нажатия как последовательные данные, это выглядит так:

 user_id timestamp duration_sec Page  545301 8/25/2020 14:49 5 home  545301 8/25/2020 15:00 10 instructor  545301 9/2/2020 13:33 5 home  545301 9/8/2020 12:46 3 home  545301 9/9/2020 11:10 3 home  545301 9/9/2020 13:24 8 general  545301 9/9/2020 14:33 12 zoom   

То, что я хочу сделать, это добавить строки в качестве разделителей между подразделами, чтобы указать, что учащийся делает перерыв между двумя сериями поведения(т. Е. Время между двумя щелчками очень велико, например, более 3 часов). Ожидаемые данные должны быть такими:

 user_id timestamp duration_sec Page  545301 8/25/2020 14:49 5 home  545301 8/25/2020 15:00 10 instructor 545301 8/25/2020 15:10 99999 break 545301 9/2/2020 13:33 5 home  545301 9/2/2020 13:38 99999 break 545301 9/8/2020 12:46 3 home 545301 9/8/2020 12:49 99999 break  545301 9/9/2020 11:10 3 home  545301 9/9/2020 13:24 8 general  545301 9/9/2020 14:33 12 zoom   

Я буду так благодарен, если кто-нибудь сможет дать мне несколько советов.

Обновление Я понял это с помощью @Abdel: сначала добавьте столбец под названием gap , в котором указано время(часы) до следующего щелчка

 df['gap']= (df['start_time_shift'] - df['start_time']).dt.total_seconds()/3600  

Во-вторых, определите функцию для добавления разрывов:

 def add_breaks(df):  List_of_breaks=[]  for i in range(len(df.index)):  if df.gap.iloc[i] gt; 3:   List_of_breaks.append(i)    for i in List_of_breaks:  line = pd.DataFrame({'course_id':df.course_id.iloc[i],  "user_id": df.user_id.iloc[i],   "start_time": df.end_time.iloc[i],   'end_time':df.start_time_shift.iloc[i],  'start_time_shift':df.start_time_shift.iloc[i 1],  'duration_min':df.start_time_shift.iloc[i]-df.end_time.iloc[i],  'gap': 'gap',  'page':'break}, index=[i 1])  df=pd.concat([df.iloc[:i 1], line, df.iloc[i 2:]]).reset_index(drop=True)     return(df)  

Я изменил ответ @Abdel, 1) изменив условие фильтра на значение gap. 2)способ добавления строк, он должен быть df.iloc[:i 1], line, df.iloc[i 2:]]).reset_index(drop=True)

Комментарии:

1. Какой должна быть структура вставленной строки? каково (например) значение duration_min добавленного столбца в break строке?

2. @Abdel Спасибо за ваш вопрос, меткой времени новой строки «перерыв» будет метка времени продолжительность последней строки, продолжительность перерыва можно определить, рассчитав разницу между меткой времени перерыва и следующим поведением. Я просто оставляю его как 99999. Поскольку моя цель состоит в том, чтобы найти последовательность поведения каждого ученика, продолжительность перерыва здесь не так важна.

3. Я уже не колебался. Взгляните на мой ответ! 😉

Ответ №1:

Вот мой ответ (на первую версию вопроса, так как некоторые данные действительно изменились между):

Сначала я создал что-то похожее на ваш фрейм данных

 import pandas as pd from io import StringIO import re  f= re.sub('s ',',',re.sub('n','..',"""user_id timestamp duration_min Page  545301 8/25/2020_14:49 8.600000 home  545301 8/25/2020_15:00 10.100000 instructor  545301 9/2/2020_13:33 49.700000 home  545301 9/8/2020_12:46 223.783333 home  545301 9/9/2020_11:10 7.633333 home  545301 9/9/2020_13:24 69.300000 general  545301 9/9/2020_14:33 2651.133333 zoom  """)).replace('..','n')  f = StringIO(f)  df= pd.read_csv(f) df['timestamp']=df['timestamp'].str.replace('_',' ',regex=False) df.drop(['Unnamed: 4'],axis=1, inplace=True) df.timestamp = pd.to_datetime(df.timestamp) print(df)  

Затем нам нужно найти индексы, в которые мы должны вставить строки :

 List_of_home_indexes=[] for i in range(len(df.index)):  if df.Page.iloc[i] =='home': List_of_home_indexes.append(i) print(List_of_home_indexes)  

Затем мы вставляем строку после ее определения, выполнив цикл по найденным индексам:

 from datetime import timedelta  for i in List_of_home_indexes:  line = pd.DataFrame({"user_id": 545301, "timestamp": df.timestamp.iloc[i]  timedelta(seconds=1), 'duration_min':99999, 'Page':'break'}, index=[i 1])  df=pd.concat([df.iloc[:i 1], line, df.iloc[i 1:]]).reset_index(drop=True)  print(df)  

Тогда вы получите желаемый результат.

Комментарии:

1. Большое вам спасибо за ваш ответ! но место, в которое я хочу вставить строку «разрыв», зависит не от клика на домашней странице, а от промежутка времени между двумя соседними щелчками. Например, между 8/25 и 9/2 есть большой разрыв, поэтому я хочу добавить здесь строку «разрыв». Я предполагаю, что если время между двумя щелчками больше X, студент делает перерыв, я немного скорректирую ваше решение для достижения этой цели, в любом случае большое вам спасибо за вашу помощь!

Ответ №2:

Для этого решения я предполагаю, что идентификатор пользователя не является индексом. Если это так, просто сбросьте индекс перед запуском.

Сначала мы определяем «время ожидания» между событиями, принимая во внимание разницу между временными метками и принимая во внимание duration_sec (который нам сначала нужно преобразовать из числа в timedelta):

 df['idle_time'] = df.timestamp.diff().shift(-1) - pd.to_timedelta(df.duration_sec, unit='s')   user_id timestamp duration_sec Page idle_time 0 545301 2020-08-25 14:49:00 5 home 0 days 00:10:55 1 545301 2020-08-25 15:00:00 10 instructor 7 days 22:32:50 2 545301 2020-09-02 13:33:00 5 home 5 days 23:12:55 3 545301 2020-09-08 12:46:00 3 home 0 days 22:23:57 4 545301 2020-09-09 11:10:00 3 home 0 days 02:13:57 5 545301 2020-09-09 13:24:00 8 general 0 days 01:08:52 6 545301 2020-09-09 14:33:00 12 zoom NaT  

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

 pre_breaks = df[df.idle_time gt; pd.to_timedelta(6, unit='h')]  user_id timestamp duration_sec Page idle_time 1 545301 2020-08-25 15:00:00 10 instructor 7 days 22:32:50 2 545301 2020-09-02 13:33:00 5 home 5 days 23:12:55 3 545301 2020-09-08 12:46:00 3 home 0 days 22:23:57  

Затем мы изменяем эти строки, чтобы они были строками разрыва, следующим образом:

 pre_breaks['timestamp'] = pre_breaks.timestamp    pd.to_timedelta(pre_breaks.duration_sec, 's') pre_breaks['Page'] = 'break' pre_breaks['duration_sec'] = pre_breaks.idle_time.apply(lambda x:x.seconds)   user_id timestamp duration_sec Page idle_time 1 545301 2020-08-25 15:00:10 81170 break NaN 2 545301 2020-09-02 13:33:05 83575 break NaN 3 545301 2020-09-08 12:46:03 80637 break NaN  

Затем мы вставляем их в индексы, соответствующие событиям, когда студент сделал перерыв:

 for i in pre_breaks.index: df.loc[i 0.5] = pre_breaks.loc[i]   user_id timestamp duration_sec Page idle_time 0.0 545301 2020-08-25 14:49:00 5 home 0 days 00:10:55 1.0 545301 2020-08-25 15:00:00 10 instructor 7 days 22:32:50 2.0 545301 2020-09-02 13:33:00 5 home 5 days 23:12:55 3.0 545301 2020-09-08 12:46:00 3 home 0 days 22:23:57 4.0 545301 2020-09-09 11:10:00 3 home 0 days 02:13:57 5.0 545301 2020-09-09 13:24:00 8 general 0 days 01:08:52 6.0 545301 2020-09-09 14:33:00 12 zoom NaT 1.5 545301 2020-08-25 15:00:10 81170 break NaT 2.5 545301 2020-09-02 13:33:05 83575 break NaT 3.5 545301 2020-09-08 12:46:03 80637 break NaT  

Наконец, мы сортируем индекс и сбрасываем его. Мы также удаляем столбец idle_time (необязательно):

 df = df.sort_index().reset_index(drop=True).drop(columns='idle_time')  

Конечный результат:

 user_id timestamp duration_sec Page 0 545301 2020-08-25 14:49:00 5 home 1 545301 2020-08-25 15:00:00 10 instructor 2 545301 2020-08-25 15:00:10 81170 break 3 545301 2020-09-02 13:33:00 5 home 4 545301 2020-09-02 13:33:05 83575 break 5 545301 2020-09-08 12:46:00 3 home 6 545301 2020-09-08 12:46:03 80637 break 7 545301 2020-09-09 11:10:00 3 home 8 545301 2020-09-09 13:24:00 8 general 9 545301 2020-09-09 14:33:00 12 zoom  

Комментарии:

1. Это работает! большое вам спасибо за вашу помощь, очень ценю это!

2. @qiweimen Я так рад! Я был бы очень рад, если бы вы отметили это как лучший ответ, я новичок и строю репутацию, ха-ха. Спасибо вам и удачи!