Разделить фрейм данных на два повторяющихся значения - PullRequest
3 голосов
/ 02 февраля 2020

У меня есть фрейм данных, который описывает статус человека:

df = pd.DataFrame({'A': [1, 2, 3, 4, 5, 6, 7, 8, 3], 
                  'B': [6, 7, 8, 9, 10, 23, 11, 12, 13], 
                  'C': ['start', 'running', 'running', 'end', 'running', 'start', 'running', 'resting', 'end']})

Этот фрейм данных записывает две поездки человека. Я хочу разделить его на основе значений столбца C, «начало» и «конец». Другие значения в столбце C не имеют значения.

Я мог бы разделить фрейм данных на следующие коды:

x=[]
y=[]

for i in range(len(df)):
    if df['C'][i]=='start':
        x.append(i)
    elif df['C'][i]=='end':
        y.append(i)

for i, j in zip(x, y):
    new_df = df.iloc[i:j+1,:]
    print(new_df)

Однако мне интересно, есть ли более эффективный способ разделите его без l oop, так как у меня довольно большой массив данных.

Ответы [ 6 ]

2 голосов
/ 02 февраля 2020

Я бы создал dict, используя GroupBy.__iter__()

Метод 1

start = df['C'].eq('start')
dfs = dict(df.loc[(start.add(df['C'].shift().eq('end')).cumsum()%2).eq(1)]
             .groupby(start.cumsum())
             .__iter__())

#{1:    A  B        C
# 0  1  6    start
# 1  2  7  running
# 2  3  8  running
# 3  4  9      end, 2:    A   B        C
# 5  6  23    start
# 6  7  11  running
# 7  8  12  resting
# 8  3  13      end}

Метод 2

start = df['C'].eq('start')
dfs = dict(df.loc[start.where(start)
                       .groupby(df['C'].shift()
                                       .eq('end')
                                       .cumsum())
                       .ffill().notna()]
             .groupby(start.cumsum())
             .__iter__())

#{1:    A  B        C
# 0  1  6    start
# 1  2  7  running
# 2  3  8  running
# 3  4  9      end, 2:    A   B        C
# 5  6  23    start
# 6  7  11  running
# 7  8  12  resting
# 8  3  13      end}

Доступ к фрейму данных

print(dfs[1])
   A  B        C
0  1  6    start
1  2  7  running
2  3  8  running
3  4  9      end

print(dfs[2])
   A   B        C
5  6  23    start
6  7  11  running
7  8  12  resting
8  3  13      end

Мы можем использовать groupby.get_group

dfs = (df.loc[start.where(start)
                   .groupby(df['C'].shift()
                                   .eq('end')
                                   .cumsum())
                       .ffill().notna()]
          .groupby(start.cumsum()))
df1=dfs.get_group(1)
df2=dfs.get_group(2) 
print(df1)
print(df2)

Подробности Метод 2

start.where(start)
0    1.0
1    NaN
2    NaN
3    NaN
4    NaN
5    1.0
6    NaN
7    NaN
8    NaN
Name: C, dtype: float64

df['C'].shift().eq('end').cumsum()


0    0
1    0
2    0
3    0
4    1
5    1
6    1
7    1
8    1
Name: C, dtype: int64

, как вы можете видеть row 4 находится в группе 1, а при использовании groupby.ffill его значение остается NaN

1 голос
/ 02 февраля 2020

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

idx = zip(df[df['C'] == 'A'].index, df[df['C'] == 'C'].index)
dfs = [df.loc[i:j] for i, j in idx]  
1 голос
/ 02 февраля 2020

Исходя из комментариев, стартовый фрейм данных:

df = pd.DataFrame({'A': [1, 2, 3, 4, 5, 6, 7, 8, 3],
                  'B': [6, 7, 8, 9, 10, 23, 11, 12, 13],
                  'C': ['start', 'running', 'running', 'end', 'running', 'start', 'running', 'resting', 'end']})

Затем:

for g in df.groupby(df.assign(tmp=(df['C'] == 'start'))['tmp'].cumsum()):
    m = (g[1]['C'] == 'end').shift().fillna(False).cumsum() == 0
    print(g[1][m])

Отпечатки:

   A  B        C
0  1  6    start
1  2  7  running
2  3  8  running
3  4  9      end
   A   B        C
5  6  23    start
6  7  11  running
7  8  12  resting
8  3  13      end
0 голосов
/ 02 февраля 2020

Я думаю, что вы можете сделать это с помощью этой строки кода:

dfs = [ df[start:end+1] 
        for start, end in zip(df.index[df['C'] == 'start'], 
                              df.index[df['C'] == 'end'])]

Вывод:

dfs[0]

   A  B        C
0  1  6    start
1  2  7  running
2  3  8  running
3  4  9      end

dfs[1]

   A   B        C
5  6  23    start
6  7  11  running
7  8  12  resting
8  3  13      end
0 голосов
/ 02 февраля 2020

с использованием str_extract | cumsum и groupby затем сохраните результаты в словаре.

df_dict = {}
counter =0 

for group, data in df.assign(
    g=df["C"].str.extract("(A|C)").bfill().apply(lambda x: x.ne("C")).cumsum()
).groupby("g"):
    counter += 1
    df_dict[counter] = data.drop('g',axis=1)

df_dict[1]
   A  B  C
0  1  6  A
1  2  7  B
2  3  8  D
3  4  9  C


df_dict[2]

   A   B  C
4  5  10  A
5  6  11  B
6  7  12  E
7  8  13  C
0 голосов
/ 02 февраля 2020

Попробуйте:

import numpy as np
df["group"]=df.groupby("C").cumcount()

df.loc[df["C"].ne("start"), "group"]=None

df["group"]=np.where(np.logical_and(df["C"].shift(1).eq("end"), df["C"].ne("start")), -1, df["group"])

df["group"]=df["group"].ffill()


dfs=[df.loc[df["group"].eq(grp)] for grp in df.groupby("group").groups]

Выходы:

#dfs[0]
   A   B        C  group
4  5  10  running   -1.0

#dfs[1]
   A  B        C  group
0  1  6    start    0.0
1  2  7  running    0.0
2  3  8  running    0.0
3  4  9      end    0.0

#dfs[2]
   A   B        C  group
5  6  23    start    1.0
6  7  11  running    1.0
7  8  12  resting    1.0
8  3  13      end    1.0
Добро пожаловать на сайт PullRequest, где вы можете задавать вопросы и получать ответы от других членов сообщества.
...