【问题标题】:Split dataframe output based on values根据值拆分数据帧输出
【发布时间】:2016-12-29 21:57:40
【问题描述】:

这篇文章涵盖了Modification of a function to return a dataframe with specified values,我想进一步修改输出。当前函数和向量化版本将得到所有列组合相互减去,并相应地返回相关数据。

示例和测试数据:

import pandas as pd
import numpy as np
from itertools import combinations

df2 = pd.DataFrame(
       {'AAA' : [80,5,6], 
        'BBB' : [85,20,30],
        'CCC' : [100,50,25],
        'DDD' : [98,50,25],
        'EEE' : [103,50,25],
        'FFF' : [105,50,25],
        'GGG' : [109,50,25]});

df2

AAA BBB CCC DDD EEE FFF GGG
0   80  85  100 98  103 105 109
1   5   20  50  50  50  50  50
2   6   30  25  25  25  25  25

v = df2.values
df3 = df2.mask((np.abs(v[:, :, None] - v[:, None]) <= 5).sum(-1) <= 1)

df3   
    AAA BBB CCC DDD EEE FFF GGG
0   80.0    85.0    100 98  103 105 109
1   NaN NaN 50  50  50  50  50
2   NaN 30.0    25  25  25  25  25

thresh 中的所有值(此处为 5)均以 np.abs &lt;=5 逐行返回。

需要改变什么?

df3 的第一行中,thresh (80,85) 和 (100,98,103,105,109) 中有两个值集群。它们都是有效的,但是是两个独立的组,不在thresh 内。我希望能够根据另一个 thresh 值来分离这些值。

我试图用下面的(有缺陷的)代码来展示我想要做什么,并且只包括这个来表明我试图自己取得进展..

df3.mask(df3.apply(lambda x : x >= df3.T.max() \
                   - (thresh * 3))).dropna(thresh=2).dropna(axis=1)


          AAA   BBB
    0   80.0    85.0

df3.mask(~df3.apply(lambda x : x >= df3.T.max() - (thresh * 3))).dropna(axis=1)


    CCC DDD EEE FFF GGG
0   100 98  103 105 109
1   50  50  50  50  50
2   25  25  25  25  25

所以我的输出很好(并且显示接近所需的输出)但是我得到这个的方式不是很好......

---所需的输出: ---

我已经使用了多行来演示,但是当我使用此代码时,它只会是需要输出和拆分的一行。因此,所需的输出是根据此示例为行 0 返回单独的列。

    CCC DDD EEE FFF GGG
0   100 98  103 105 109

         AAA    BBB
    0   80.0    85.0

【问题讨论】:

    标签: python pandas


    【解决方案1】:

    我觉得这值得单独回答。

    我编写了一个对一维数组进行操作的聚类函数。我知道如何将它进一步矢量化为二维,但我还没有得到它。事实上,我使用np.apply_along_axis

    此功能在此answer 到此question 中进行了描述。我鼓励您点击链接并查看为获得这个看似简单的功能所做的工作。

    它的作用是在由每个点的左右边距定义的数组中找到簇。它排序,然后聚类,然后取消排序。

    增量聚类函数

    def delta_cluster(a, dleft, dright):
        s = a.argsort()
        y = s.argsort()
        a = a[s]
        rng = np.arange(len(a))
    
        edge_left = a.searchsorted(a - dleft)
        starts = edge_left == rng
    
        edge_right = np.append(0, a.searchsorted(a + dright, side='right')[:-1])
        ends = edge_right == rng
    
        return (starts & ends).cumsum()[y]
    

    解决手头的问题

    df2 中的每一行和np.apply_along_axis 使用集群函数,并构造一个名为clustersDataFrame,它与df2 镜像相同的索引和列。然后stack 得到一个Series,这样以后操作起来会更容易。

    clusters = pd.DataFrame(
        np.apply_along_axis(delta_cluster, 1, df2.values, 10, 10),
        df2.index, df2.columns).stack()
    

    这描述了下一个代码块。

    • 当我执行groupby 时,我需要保留df2 的行信息。
    • 使用transform 获取每行的簇大小。
    • stack df2 的值并将集群值作为索引的一部分附加。这可以实现您正在寻找的分离。
    • mask val 其中size 等于 1。这些是单例集群。

    lvl0 = clusters.index.get_level_values(0)
    size = clusters.groupby([lvl0, clusters]).transform('size')
    
    val = df2.stack().to_frame('value').set_index(clusters, append=True).value
    
    val.mask(size.values == 1).dropna().unstack(1)
    
          AAA   BBB    CCC   DDD    EEE    FFF    GGG
    0 1  80.0  85.0    NaN   NaN    NaN    NaN    NaN
      2   NaN   NaN  100.0  98.0  103.0  105.0  109.0
    1 3   NaN   NaN   50.0  50.0   50.0   50.0   50.0
    2 2   NaN  30.0   25.0  25.0   25.0   25.0   25.0
    

    这符合您的结果,除了我将第一行分成两行。

         AAA   BBB    CCC   DDD    EEE    FFF    GGG
    0   80.0  85.0    100    98    103    105    109
    1    NaN   NaN     50    50     50     50     50
    2    NaN  30.0     25    25     25     25     25
    

    【讨论】:

    • 我从洞里爬出来了 ;-)
    • 今天完成工作后,我会花一些时间来了解它是如何工作的。它看起来非常令人印象深刻。
    • @adele 我很高兴能帮上忙。很有趣!
    【解决方案2】:

    我认为您可以尝试以不同的方式解决您的问题。这个想法是在每一行中获得“间隙和岛屿”并标记每个组:

    因此,首先 - 将您的列放入行并在每个初始行索引中对值进行排序:

    >>> df = df2.stack().sort_values().sortlevel(0, sort_remaining=False)
    >>> df
    0  AAA     80
       BBB     85
       DDD     98
       CCC    100
       EEE    103
       FFF    105
       GGG    109
    1  AAA      5
       BBB     20
       GGG     50
       FFF     50
       DDD     50
       CCC     50
       EEE     50
    2  AAA      6
       GGG     25
       EEE     25
       DDD     25
       CCC     25
       FFF     25
       BBB     30
    

    接下来,使用“prev values”和当前值创建新的 DataFrame:

    >>> df = df2.stack().sort_values().sortlevel(0, sort_remaining=False)
    >>> df = pd.concat([df, df.groupby(level=0).shift(1)], axis=1)
    >>> df.columns = ['cur', 'prev']
    >>> df
           cur   prev
    0 AAA   80    NaN
      BBB   85   80.0
      DDD   98   85.0
      CCC  100   98.0
      EEE  103  100.0
      FFF  105  103.0
      GGG  109  105.0
    1 AAA    5    NaN
      BBB   20    5.0
      GGG   50   20.0
      FFF   50   50.0
      DDD   50   50.0
      CCC   50   50.0
      EEE   50   50.0
    2 AAA    6    NaN
      GGG   25    6.0
      EEE   25   25.0
      DDD   25   25.0
      CCC   25   25.0
      FFF   25   25.0
      BBB   30   25.0
    

    现在,创建岛屿标签:

    >>> df = (df['cur'] - df['prev'] > thresh).astype('int')
    >>> df
    0  AAA    0
       BBB    0
       DDD    1
       CCC    0
       EEE    0
       FFF    0
       GGG    0
    1  AAA    0
       BBB    1
       GGG    1
       FFF    0
       DDD    0
       CCC    0
       EEE    0
    2  AAA    0
       GGG    1
       EEE    0
       DDD    0
       CCC    0
       FFF    0
       BBB    0
    
    >>> df.groupby(level=0).cumsum().unstack()
       AAA  BBB  CCC  DDD  EEE  FFF  GGG
    0    0    0    1    1    1    1    1
    1    0    1    2    2    2    2    2
    2    0    1    1    1    1    1    1
    

    现在你可以过滤掉只有一个成员的组,你就完成了:)

    >>> dfm = df.groupby(level=0).cumsum().unstack()
    >>> dfm
       AAA  BBB  CCC  DDD  EEE  FFF  GGG
    0    0    0    1    1    1    1    1
    1    0    1    2    2    2    2    2
    2    0    1    1    1    1    1    1
    
    >>> df2[dfm == 0].loc[0:0].dropna(axis=1)
       AAA   BBB
    0   80  85.0
    >>> df2[dfm == 1].loc[0:0].dropna(axis=1)
         CCC   DDD    EEE    FFF    GGG
    0  100.0  98.0  103.0  105.0  109.0
    

    【讨论】:

    • 嗨@Roman Pekar。感谢您的关注。您能告诉我如何使用它来提供帖子底部显示的所需输出吗?
    • 看起来很有趣。即使数据集更大,通过df['cur'] - df['prev'] &gt; thresh 创建的索引和岛的这种转变是否涵盖所有组合?当我们移动 prev('30' 从'BBB')时丢失其中一个值是否重要。它看起来很棒,只是想了解它如何与我的简单大脑一起工作:-)
    • 是的,它应该涵盖所有组合。这里的想法是为每个“大跳跃”行(其中与前一个的差异大于阈值)获取 1,为其他行获取 0,然后将其累加起来。最后,每个组都有一个标签
    • 第一行的那些 NaN 值没问题 - 你永远不会有 1,所以最小值总是有 label = 0
    • 因此,如果我们有 5 个组,则需要测试 '[dfm == 3]' 和 '[dfm == 4]' 等?
    【解决方案3】:

    方法 1
    我复制并粘贴了上一个问题,包括细微的变化。


    我矢量化并嵌入了您的 closeCols 以获得一些令人麻木的乐趣。
    注意没有apply

    • numpy 广播 让所有列组合相互减去。
    • np.abs
    • &lt;= 5
    • sum(-1) 我安排了广播,这样说行0、列AAA 与所有行0 的差异将在最后一个维度上布置。 sum(-1) 中的 -1 表示对最后一个维度求和。
    • &lt;= 1所有值距离自己都小于5。所以我希望这些总和大于 1。因此,我们屏蔽所有小于或等于 1。

    df2 = pd.DataFrame(
           {'AAA' : [80,5,6], 
            'BBB' : [85,20,30],
            'CCC' : [100,50,25],
            'DDD' : [98,50,25],
            'EEE' : [103,50,25],
            'FFF' : [105,50,25],
            'GGG' : [109,50,25]});
    
    v = df2.values
    
    # let delta be the distance threshold
    # let k be the cluster size threshold
    x, k = 5, 2  #  cluster size must be greater than k
    df2.mask((np.abs(v[:, :, None] - v[:, None]) <= x).sum(-1) <= k)
    # note that this is the same as before but k = 1 was hard coded
    
    print(df3)   
    
       AAA   BBB  CCC  DDD  EEE  FFF   GGG
    0  NaN   NaN  100   98  103  105   NaN
    1  NaN   NaN   50   50   50   50  50.0
    2  NaN  30.0   25   25   25   25  25.0
    

    【讨论】:

    • 感谢@piRSquared。我标记了答案,因为它在查找某些集群大小时非常有用。
    • @adele 这个问题让我陷入了困境。我一直在制定一个复杂且可扩展的解决方案。最终,我将能够使用这种方法识别集群。当我这样做时,我会更新这个答案。
    • 非常感谢您关注这个@piRSquared
    • @adele 这不全是利他主义。我喜欢有趣的问题。这对我也将非常有用。但我很高兴它也可以帮助其他人。
    猜你喜欢
    • 1970-01-01
    • 1970-01-01
    • 2021-05-14
    • 2018-05-05
    • 2018-03-09
    • 2022-10-02
    • 2021-04-16
    • 1970-01-01
    • 1970-01-01
    相关资源
    最近更新 更多