2016-05-26 3 views
1

У меня есть кадр данных панды, который выглядит так:Python Панды - объединение 2 строки из кадра данных - с условием

A  B  C Stime Etime  
1220627 a 10.0 18:00:00 18:09:59 
1220627 a 12.0 18:15:00 18:26:59 
1220683 b 3.0 18:36:00 18:38:59 
1220683 a 3.0 18:36:00 18:38:59 
1220732 a 59.0 18:00:00 18:58:59 
1220760 A 16.0 18:24:00 18:39:59 
1220760 a 16.0 18:24:00 18:39:59 
1220760 A 19.0 18:40:00 18:58:59 
1220760 b 19.0 18:40:00 18:58:59 
1220760 a 19.0 18:40:00 18:58:59 
1220775 a 3.0 18:03:00 18:05:59 

STIME и Etime перевалы находятся от типа DateTime.

C - это количество минут между Stime и Etime.

Кол-во семейных удостоверений личности и B col является идентификатором личности в семье.

(чтобы cols A и B вместе представляли собой уникальное лицо).

Что мне нужно сделать, чтобы обновить таблицу такой, что если для определенного человека, то STIME приходит сразу после окончания времени - я буду блок 2 строки и я буду обновлять C.

, например, здесь, для человека a в HH 1220760 первого Etime является 18:39:59

и второй Stime является 18:40:00 - которая приходит сразу после 18:39:59, поэтому я хотел бы, чтобы блок линии и обновление C для этого человека быть 35 (16 + 19).

Я попытался использовать groupby, но я не знаю, как добавить условие, что Stime поступит сразу после Etime.

+0

делает случай вопроса письмо в колонке B? это «А» так же, как «а»? – piRSquared

+0

нет, его не то же самое. Дело имеет значение. –

ответ

3

Если добавить одну секунду в Etime то можно найти строки, которые будут соединены группирование по ['A', 'B'], а затем для каждой группы сравнения сдвинуты Etime с с следующей Stime:

df['Etime'] += pd.Timedelta(seconds=1) 
df = df.sort_values(by=['A', 'B', 'Stime']) 
df['keep'] = df.groupby(['A','B'])['Etime'].shift(1) != df['Stime'] 
#   A B  C    Etime    Stime keep 
# 0 1220627 a 10.0 2016-05-29 18:10:00 2016-05-29 18:00:00 True 
# 1 1220627 a 12.0 2016-05-29 18:27:00 2016-05-29 18:15:00 True 
# 3 1220683 a 3.0 2016-05-29 18:39:00 2016-05-29 18:36:00 True 
# 2 1220683 b 3.0 2016-05-29 18:39:00 2016-05-29 18:36:00 True 
# 4 1220732 a 59.0 2016-05-29 18:59:00 2016-05-29 18:00:00 True 
# 5 1220760 A 16.0 2016-05-29 18:40:00 2016-05-29 18:24:00 True 
# 7 1220760 A 19.0 2016-05-29 18:59:00 2016-05-29 18:40:00 False 
# 12 1220760 a 0.0 2016-05-29 18:10:00 2016-05-29 18:00:00 True 
# 6 1220760 a 16.0 2016-05-29 18:40:00 2016-05-29 18:24:00 True 
# 9 1220760 a 19.0 2016-05-29 18:59:00 2016-05-29 18:40:00 False 
# 11 1220760 a 11.0 2016-05-29 19:10:00 2016-05-29 18:59:00 False 
# 8 1220760 b 19.0 2016-05-29 18:59:00 2016-05-29 18:40:00 True 
# 10 1220775 a 3.0 2016-05-29 18:06:00 2016-05-29 18:03:00 True 

Мы хотим сохранить строки где keep имеет значение True и удаляет строки, где keep - False, , за исключением того, что мы также хотим обновить Etime s, если необходимо.

Было бы хорошо, если бы мы могли назначить «групповой номер» для каждой строки, чтобы мы могли группироваться по ['A', 'B', 'group_number'] - и на самом деле мы можем.Все, что нам нужно сделать, это применить cumsum к keep колонок:

df['group_number'] = df.groupby(['A','B'])['keep'].cumsum() 
#   A B  C    Etime    Stime keep group_number 
# 0 1220627 a 10.0 2016-05-29 18:10:00 2016-05-29 18:00:00 True   1.0 
# 1 1220627 a 12.0 2016-05-29 18:27:00 2016-05-29 18:15:00 True   2.0 
# 3 1220683 a 3.0 2016-05-29 18:39:00 2016-05-29 18:36:00 True   1.0 
# 2 1220683 b 3.0 2016-05-29 18:39:00 2016-05-29 18:36:00 True   1.0 
# 4 1220732 a 59.0 2016-05-29 18:59:00 2016-05-29 18:00:00 True   1.0 
# 5 1220760 A 16.0 2016-05-29 18:40:00 2016-05-29 18:24:00 True   1.0 
# 7 1220760 A 19.0 2016-05-29 18:59:00 2016-05-29 18:40:00 False   1.0 
# 12 1220760 a 0.0 2016-05-29 18:10:00 2016-05-29 18:00:00 True   1.0 
# 6 1220760 a 16.0 2016-05-29 18:40:00 2016-05-29 18:24:00 True   2.0 
# 9 1220760 a 19.0 2016-05-29 18:59:00 2016-05-29 18:40:00 False   2.0 
# 11 1220760 a 11.0 2016-05-29 19:10:00 2016-05-29 18:59:00 False   2.0 
# 8 1220760 b 19.0 2016-05-29 18:59:00 2016-05-29 18:40:00 True   1.0 
# 10 1220775 a 3.0 2016-05-29 18:06:00 2016-05-29 18:03:00 True   1.0 

Теперь желаемый результат может быть найден путем группирования по ['A', 'B', 'group_number'], и найти минимальную Stime и максимальную Etime для каждой группы:

result = df.groupby(['A','B', 'group_number']).agg({'Stime':'min', 'Etime':'max'}) 

            Stime    Etime 
A  B group_number           
1220627 a 1.0   2016-05-29 18:00:00 2016-05-29 18:10:00 
      2.0   2016-05-29 18:15:00 2016-05-29 18:27:00 
1220683 a 1.0   2016-05-29 18:36:00 2016-05-29 18:39:00 
     b 1.0   2016-05-29 18:36:00 2016-05-29 18:39:00 
1220732 a 1.0   2016-05-29 18:00:00 2016-05-29 18:59:00 
1220760 A 1.0   2016-05-29 18:24:00 2016-05-29 18:59:00 
     a 1.0   2016-05-29 18:00:00 2016-05-29 18:10:00 
      2.0   2016-05-29 18:24:00 2016-05-29 19:10:00 
     b 1.0   2016-05-29 18:40:00 2016-05-29 18:59:00 
1220775 a 1.0   2016-05-29 18:03:00 2016-05-29 18:06:00 

Собираем все вместе,

import numpy as np 
import pandas as pd 

df = pd.DataFrame(
    {'A': [1220627, 1220627, 1220683, 1220683, 1220732, 1220760, 1220760, 
      1220760, 1220760, 1220760, 1220775, 1220760, 1220760], 
    'B': ['a', 'a', 'b', 'a', 'a', 'A', 'a', 'A', 'b', 'a', 'a', 'a', 'a'], 
    'C': [10.0, 12.0, 3.0, 3.0, 59.0, 16.0, 16.0, 19.0, 19.0, 19.0, 3.0, 11.0, 0], 
    'Stime': ['18:00:00', '18:15:00', '18:36:00', '18:36:00', '18:00:00', 
       '18:24:00', '18:24:00', '18:40:00', '18:40:00', '18:40:00', 
       '18:03:00', '18:59:00', '18:00:00'], 
    'Etime': ['18:09:59', '18:26:59', '18:38:59', '18:38:59', '18:58:59', 
       '18:39:59', '18:39:59', '18:58:59', '18:58:59', '18:58:59', 
       '18:05:59', '19:09:59', '18:09:59'],}) 
for col in ['Stime', 'Etime']: 
    df[col] = pd.to_datetime(df[col]) 
df['Etime'] += pd.Timedelta(seconds=1) 
df = df.sort_values(by=['A', 'B', 'Stime']) 
df['keep'] = df.groupby(['A','B'])['Etime'].shift(1) != df['Stime'] 
df['group_number'] = df.groupby(['A','B'])['keep'].cumsum() 
result = df.groupby(['A','B', 'group_number']).agg({'Stime':'min', 'Etime':'max'}) 
result = result.reset_index() 
result['C'] = (result['Etime']-result['Stime']).dt.total_seconds()/60.0 
result = result[['A', 'B', 'C', 'Stime', 'Etime']] 
print(result) 

выходы

  A B  C    Stime    Etime 
0 1220627 a 10.0 2016-05-29 18:00:00 2016-05-29 18:10:00 
1 1220627 a 12.0 2016-05-29 18:15:00 2016-05-29 18:27:00 
2 1220683 a 3.0 2016-05-29 18:36:00 2016-05-29 18:39:00 
3 1220683 b 3.0 2016-05-29 18:36:00 2016-05-29 18:39:00 
4 1220732 a 59.0 2016-05-29 18:00:00 2016-05-29 18:59:00 
5 1220760 A 35.0 2016-05-29 18:24:00 2016-05-29 18:59:00 
6 1220760 a 10.0 2016-05-29 18:00:00 2016-05-29 18:10:00 
7 1220760 a 46.0 2016-05-29 18:24:00 2016-05-29 19:10:00 
8 1220760 b 19.0 2016-05-29 18:40:00 2016-05-29 18:59:00 
9 1220775 a 3.0 2016-05-29 18:03:00 2016-05-29 18:06:00 

Одним из преимуществ использования полуоткрытых интервалов вида [start, end) вместо того, чтобы полностью замкнутые интервалы [start, end] в том, что, когда два интервала примыкают, в end из одного равноstart следующего.

Другим преимуществом является то, что количество минут в полуоткрытом интервале равно end-start. При полностью закрытом интервале формула становится end-start+1.

Встроенный в Python range и список разрезанных синтаксиса используют полуоткрытые интервалы for these same reasons. Поэтому я порекомендовал бы использовать полуоткрытые интервалы [Stime, Etime) в вашем DataFrame .

+0

что, если есть более чем 2 линии, чтобы объединить, например: '1220760 16.0 18:24:00 18: 39: 59' ' 1220760 19,0 18:40:00 18: 58: 59' '1220760 a 11.0 18:59:00 19: 09: 59' –

1

что об этом подходе?

In [68]: df.groupby(['A','B', df.Stime - df['Etime'].shift() <= pd.Timedelta('1S')], as_index=False)['C'].sum() 
Out[68]: 
     A B  C 
0 1220627 a 22.0 
1 1220683 a 3.0 
2 1220683 b 3.0 
3 1220732 a 59.0 
4 1220760 A 35.0 
5 1220760 a 35.0 
6 1220760 b 19.0 
7 1220775 a 3.0 
0

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

предполагая df = данные, которые вы предоставили выше:

df['Stime'] = pd.to_datetime(df['Stime'], format='%H:%M:%S') # needs to be converted to datetime 
df['Etime'] = pd.to_datetime(df['Etime'], format='%H:%M:%S') # needs to be converted to datetime 

df = df.sort_values(['A','B','Stime']) # data needs to be sorted by unique person : Stime 
df = df.reset_index(drop=True) 
df = df.reset_index() 

def new_person(row): 
    if row.name > 0: 
     if row['A'] != df.ix[row.name-1][1] or row['B'] != df.ix[row.name-1][2]: 
      return 'Yes' 

def update(row): 
    if row.name > 0: 
     if row['B'] == df.ix[row.name-1][2]: 
      if df.ix[row.name][4] - df.ix[row.name-1][5] >= pd.Timedelta(seconds=0) and df.ix[row.name][4] - df.ix[row.name-1][5] < pd.Timedelta(seconds=2): 
       return df.groupby(['A','B'])['C'].cumsum().ix[row.name] 

def rewrite(row): 
    if row['update'] > 0: 
     return row['update'] 
    else: 
     return row['C'] 

df['new_person'] = df.apply(new_person, axis=1) # adds column where value = 'Yes' if person is not the same as row above 
df['update'] = df.apply(update,axis=1) # adds a column 'update' to allow for a cumulative sum rewritten to 'C' in rewrite function 
print df 

df['Stime'] = pd.to_datetime(df['Stime'], format='%H:%M:%S').dt.time # removes date from datetime 
df['Etime'] = pd.to_datetime(df['Etime'], format='%H:%M:%S').dt.time # removes date from datetime 
df['C'] = df.apply(rewrite,axis=1) # rewrites values for 'C' column 

# hacky way of combining idxmax and indices of rows where the person is 'new' 
updated = df.groupby(['A','B'])['C'].agg(pd.Series.idxmax).values 
not_updated = df['new_person'].isnull().tolist() 

combined = [x for x in df.index if (x in updated or x in not_updated)] 

df = df.iloc[combined] 
df = df.drop(['new_person','update','index'],axis=1) 
print df 

Извинения за чрезвычайно Hacky ответ, но я думаю, что он должен добиться того, что вам нужно. Не уверен, насколько хорошо он будет работать, если ваш dataframe очень большой.

Результирующая dataframe:

  A B C  Stime  Etime 
0 1220627 a 10 18:00:00 18:09:59 
1 1220627 a 12 18:15:00 18:26:59 
2 1220683 a 3 18:36:00 18:38:59 
3 1220683 b 3 18:36:00 18:38:59 
4 1220732 a 59 18:00:00 18:58:59 
6 1220760 A 35 18:40:00 18:58:59 
9 1220760 a 46 18:59:00 18:09:59 
10 1220760 b 19 18:40:00 18:58:59 
11 1220775 a 3 18:03:00 18:05:59 

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

  • Нет связанных вопросов^_^