Разделить DataFrame на куски

1

У меня есть DataFrame, который содержит имя, год, тег и множество других переменных. Таким образом, это может выглядеть так

df = pd.DataFrame({
    "name": 4*["A"] + 5*["B"],
    "year": [1999,2000,2001,2002,2010,2011,2012,2013,2014],
    "tag": [0,1,0,0,1,0,0,1,0],
    "x1": np.random.normal(size=9),
    "x2": np.random.uniform(size=9)
})

print df

  name  tag        x1        x2  year
0    A    0 -1.352707  0.932559  1999
1    A    1 -1.359828  0.724635  2000
2    A    0  1.289980  0.477135  2001
3    A    0 -0.409960  0.863443  2002
4    B    1 -1.469220  0.324349  2010
5    B    0  0.372617  0.871734  2011
6    B    0 -0.047398  0.307596  2012
7    B    1  1.240108  0.667082  2013
8    B    0  0.558432  0.284363  2014

Я ищу способ сгруппировать или разделить DataFrame на куски, где каждый кусок должен содержать

  1. одна строка с тегом == 1 и
  2. все строки, где есть тег == 0, строка [год + 1] и строка [год-1], строка [[год + -1, "тег"]] == 1 и строка [[год + -1, "имя"] ] == строка [[год, "название"]].

Simpy put, я хочу куски размером 3, где средний ряд отмечен и окружен двумя непомеченными рядами одной и той же компании. Таким образом, в приведенном выше примере только два куска, которые проходят эти условия,

  name  tag        x1        x2  year
0    A    0 -1.352707  0.932559  1999
1    A    1 -1.359828  0.724635  2000
2    A    0  1.289980  0.477135  2001

а также

7    B    0 -0.047398  0.307596  2012
8    B    1  1.240108  0.667082  2013
9    B    0  0.558432  0.284363  2014

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

Буду признателен за любые идеи.

  • 0
    Ваш вопрос немного неясен относительно того, как вы хотите получить данные. Хотите ли вы синтаксис, возвращающий один из фрагментов, или все фрагменты в одном DataFrame ?
  • 0
    Я хотел бы код, который либо возвращает следующий блок или DataFrame всех блоков.
Теги:
pandas
dataframe
pandas-groupby

2 ответа

3
Лучший ответ

Попробуем этот бит логики:

df = pd.DataFrame({
    "name": 4*["A"] + 5*["B"],
    "year": [1999,2000,2001,2002,2010,2011,2012,2013,2014],
    "tag": [0,1,0,0,1,0,0,1,0],
    "x1": np.random.normal(size=9),
    "x2": np.random.uniform(size=9)
})

grp = df.groupby(['name',
                df.tag.cumsum().rolling(3, center=True, min_periods=1).max()])

chunks_df = {}
for n, g in grp:
    if g.shape[0] >= 3:
        chunks_df[n] = g

Где chunks_df - словарь вашего разбитого кадра данных:

chunks_df[('A', 1.0)]

  name  year  tag        x1        x2
0    A  1999    0 -0.015852  0.553314
1    A  2000    1  0.367290  0.245546
2    A  2001    0  0.605592  0.524358

chunks_df[('B', 3.0)]

  name  year  tag        x1        x2
6    B  2012    0 -0.750010  0.432032
7    B  2013    1 -0.682009  0.971042
8    B  2014    0  1.066113  0.179048

Подробности:

  • Используйте cumsum для уникальной идентификации/метки каждого тега == 1.
  • Используйте roll с окном 3 и получите максимальное значение этого центрированного окна, чтобы выбрать -1, 1 и +1.
  • 0
    Это блестяще! Спасибо!
  • 0
    @Lxndr Спасибо. Пожалуйста. Удачного кодирования!
1

Хотя ответ @ScottBoston отлично подходит для DataFrame, который я дал в вопросе, он не работает в тех случаях, когда отсутствует год. Так, например, в случае

df = pd.DataFrame({
    "name": 4*["A"] + 6*["B"],
    "year": [1999,2000,2001,2002,2008,2010,2011,2012,2013,2014],
    "tag": [0,1,0,0,0,1,0,0,1,0],
    "x1": np.random.normal(size=10),
    "x2": np.random.uniform(size=10)
})  


print df

  name  tag        x1        x2  year
0    A    0 -0.387840  0.729721  1999
1    A    1 -0.112094  0.813332  2000
2    A    0  0.913186  0.115521  2001
3    A    0 -1.088056  0.983111  2002
4    B    0  0.037521  0.743706  2008
5    B    1  0.602878  0.007256  2010
6    B    0 -0.340498  0.961602  2011
7    B    0  0.170654  0.293789  2012
8    B    1  0.973555  0.942687  2013
9    B    0 -0.643503  0.133091  2014

код даст

grp = df.groupby(['name',
                df.tag.cumsum().rolling(3, center=True, min_periods=1).max()])

chunks_df = {}
for n, g in grp:
    if g.shape[0] >= 3:
        chunks_df[n] = g
        print n
        print g, "\n"    


('A', 1.0)
  name  tag        x1        x2  year
0    A    0 -0.387840  0.729721  1999
1    A    1 -0.112094  0.813332  2000
2    A    0  0.913186  0.115521  2001
3    A    0 -1.088056  0.983111  2002 

('B', 2.0)
  name  tag        x1        x2  year
4    B    0  0.037521  0.743706  2008
5    B    1  0.602878  0.007256  2010
6    B    0 -0.340498  0.961602  2011 

('B', 3.0)
  name  tag        x1        x2  year
7    B    0  0.170654  0.293789  2012
8    B    1  0.973555  0.942687  2013
9    B    0 -0.643503  0.133091  2014

который показывает, что размер первого фрагмента неправильный, и второй кусок не должен присутствовать в соответствии со вторым условием в исходном вопросе (годы - 2008, 2010 и 2011).

Проблемы с двумя мужчинами

  1. Вопрос явно скрывает возможность того, что строка будет содержать более одного фрагмента, поэтому одного дополнительного индекса вообще не может быть достаточно.
  2. Должно быть включено условие по годам, поэтому раскатный расчет должен быть в два столбца (тег и год) одновременно, который в настоящее время не поддерживается пандами в соответствии с qaru.site/questions/456684/...,

Итак, теперь я общаюсь следующим образом

def rolling(df, func, window_size=3):
    dxl = int(window_size/2)    
    if window_size % 2 == 0:
        dxu = dxl
    else:
        dxu = dxl+1
    xmin = dxl
    xmax = len(df)-dxu+1

    for i in xrange(xmin,xmax):
        chunk = df.iloc[i-dxl:i+dxu,:]
        if func(chunk):
            yield chunk



def valid(chunk):
    if len(chunk.name.value_counts()) != 1:
        return False
    if chunk.tag.iloc[1] != 1:
        return False
    if chunk.year.iloc[2]-chunk.year.iloc[0] != 2:
        return False
    return True



new_df = pd.DataFrame()
for ichunk, chunk in enumerate(rolling(df, window_size=3, func=valid)):
    new_df = new_df.append(chunk.assign(new_tag=ichunk), ignore_index=True)

for name, g in new_df.groupby(["name","new_tag"]):
    print name
    print g,"\n"


('A', 0)
  name  tag        x1        x2  year  new_tag
0    A    0 -1.046241  0.692206  1999        0
1    A    1  0.373060  0.919130  2000        0
2    A    0  1.316474  0.463517  2001        0 

('B', 1)
  name  tag        x1        x2  year  new_tag
3    B    0  0.376408  0.743188  2012        1
4    B    1  0.019062  0.647851  2013        1
5    B    0 -0.442368  0.506169  2014        1 

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

  • 0
    Большое улучшение +1

Ещё вопросы

Сообщество Overcoder
Наверх
Меню