#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 Я так рад! Я был бы очень рад, если бы вы отметили это как лучший ответ, я новичок и строю репутацию, ха-ха. Спасибо вам и удачи!