首页 文章

将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和

  • 存在tag == 0,row [year 1]和row [year-1]的所有行,row [[year -1,"tag"]] == 1和row [[year -1,"name"]] == row [[年,"name"]] .

Simpy说,我想要大小为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循环中)另一个列,它为每个块提供一个新ID,然后我可以将其分组 . 然而,我对这种方法非常不满意,因为它看起来既不高效又不优雅 .

我很欣赏任何想法 .

2 回答

  • 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年),显示第一个块的大小是错误的,第二个块不应该在那里 .

    这两个人的问题是

    • 问题明确地保持了一行在多个块中的可能性,因此一个额外的索引通常不够 .

    • 必须包含年份条件,因此滚动计算需要同时在两列(标签和年份)上,根据https://stackoverflow.com/a/37491779/2336654,目前pandas不支持 .

    所以我现在的解决方法如下

    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
    

    只是想我应该添加这个,以防将来有人想知道为什么接受的答案不适用于类似的问题 .

  • 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 .

    • 使用窗口为3的滚动并获取该居中窗口的最大值,以选择-1,1和1 .

相关问题