【问题标题】:Move non-empty cells to the left in pandas DataFrame在 pandas DataFrame 中将非空单元格向左移动
【发布时间】:2015-08-18 01:26:40
【问题描述】:

假设我有表单的数据

Name    h1    h2    h3    h4
A       1     nan   2     3
B       nan   nan   1     3
C       1     3     2     nan

我想将所有非 nan 单元格向左移动(或在新列中收集所有非 nan 数据),同时保持从左到右的顺序,得到

Name    h1    h2    h3    h4
A       1     2     3     nan
B       1     3     nan   nan
C       1     3     2     nan

我当然可以逐行这样做。但我希望知道是否还有其他性能更好的方法。

【问题讨论】:

    标签: python pandas


    【解决方案1】:

    首先,使用np.isnan 创建一个布尔数组,这会将NaN 标记为True,将非nan 值标记为False,然后对它们进行argsort,这样您将保持非nan 值和NaN 的顺序被推到右边。

    idx = np.isnan(df.values).argsort(axis=1)
    df = pd.DataFrame(
        df.values[np.arange(df.shape[0])[:, None], idx],
        index=df.index,
        columns=df.columns,
    )
    
           h1   h2   h3  h4
    Name
    A     1.0  2.0  3.0 NaN
    B     1.0  3.0  NaN NaN
    C     1.0  3.0  2.0 NaN
    

    详情

    np.isnan(df.values)
    # array([[False,  True, False, False],
    #        [ True,  True, False, False],
    #        [False, False, False,  True]])
    
    # False ⟶ 0 True ⟶ 1
    # When sorted all True values i.e nan are pushed to the right.
    
    idx = np.isnan(df.values).argsort(axis=1)
    # array([[0, 2, 3, 1],
    #        [2, 3, 0, 1],
    #        [0, 1, 2, 3]], dtype=int64)
    
    # Now, indexing `df.values` using `idx`
    df.values[np.arange(df.shape[0])[:, None], idx]
    # array([[ 1.,  2.,  3., nan],
    #        [ 1.,  3., nan, nan],
    #        [ 1.,  3.,  2., nan]])
    
    # Make that as a DataFrame
    df = pd.DataFrame(
        df.values[np.arange(df.shape[0])[:, None], idx],
        index=df.index,
        columns=df.columns,
    )
    
    #        h1   h2   h3  h4
    # Name
    # A     1.0  2.0  3.0 NaN
    # B     1.0  3.0  NaN NaN
    # C     1.0  3.0  2.0 NaN
    

    【讨论】:

    • 如果您的列数较多,使用argsort(axis=1, kind="stable") 保留顺序会更安全。
    【解决方案2】:

    这就是我所做的:

    我将您的数据框拆分为更长的格式,然后按名称列分组。在每个组中,我删除了 NaN,但随后重新索引到完整的 h1 思想 h4 集,从而在右侧重新创建您的 NaN。

    from io import StringIO
    import pandas
    
    def defragment(x):
        values = x.dropna().values
        return pandas.Series(values, index=df.columns[:len(values)])
    
    datastring = StringIO("""\
    Name    h1    h2    h3    h4
    A       1     nan   2     3
    B       nan   nan   1     3
    C       1     3     2     nan""")
    
    df = pandas.read_table(datastring, sep='\s+').set_index('Name')
    long_index = pandas.MultiIndex.from_product([df.index, df.columns])
    
    print(
        df.stack()
          .groupby(level='Name')
          .apply(defragment)
          .reindex(long_index)  
          .unstack()  
    )
    

    所以我得到:

       h1  h2  h3  h4
    A   1   2   3 NaN
    B   1   3 NaN NaN
    C   1   3   2 NaN
    

    【讨论】:

    • 我似乎记得有一个“技巧”可以有效地做到这一点,但不记得了。我认为是@DSM。
    • 它按描述工作。谢谢!我会去挖掘你使用的方法的文档。
    • @AndyHayden 你的答案去哪儿了?好多了!
    • @PaulH 这是错误的!正如鲁路修所指出的,排序破坏了排序。这里肯定有窍门....
    • 快点到 10k 就可以看到已删除的答案了:p
    【解决方案3】:

    首先,制作函数。

            def squeeze_nan(x):
                original_columns = x.index.tolist()
    
                squeezed = x.dropna()
                squeezed.index = [original_columns[n] for n in range(squeezed.count())]
    
                return squeezed.reindex(original_columns, fill_value=np.nan)
    

    其次,应用函数。

    df.apply(squeeze_nan, axis=1)
    

    你也可以尝试axis=0.[::-1]将nan挤压到任意方向。

    [编辑]

    @Mxracer888 你想要这个吗?

    def squeeze_nan(x, hold):
        if x.name not in hold:
            original_columns = x.index.tolist()
    
            squeezed = x.dropna()
            squeezed.index = [original_columns[n] for n in range(squeezed.count())]
    
            return squeezed.reindex(original_columns, fill_value=np.nan)
        else:
            return x
    
    df.apply(lambda x: squeeze_nan(x, ['B']), axis=1)
    

    【讨论】:

    • 我知道这是一个死线。但是我遇到了它,这个特定的答案主要对我有用。在我需要转移的那些我需要保持不受影响的列之后,我有更多的列。如何将此修改为仅“移动”几个特定列的范围?
    • @Mxracer888 请检查编辑。如果这不是您想要的,请告诉我您想要的 I/O。
    • 怎么样?名称 h1 h2 h3 h4 A 1 nan 2 3 B 1 3 nan nan C 1 3 2 nan
    【解决方案4】:

    以下是使用正则表达式的方法(可能不推荐):

    pd.read_csv(StringIO(re.sub(',+',',',df.to_csv())))
    Out[20]: 
      Name  h1  h2  h3  h4
    0    A   1   2   3 NaN
    1    B   1   3 NaN NaN
    2    C   1   3   2 NaN
    

    【讨论】:

    • 你能发布你从中得到的结果吗?尝试此操作时,我没有看到 OP 所需的输出。
    • 啊,我没有正确阅读问题,你觉得我的新答案怎么样 ;)
    猜你喜欢
    • 2017-01-14
    • 2014-06-10
    • 1970-01-01
    • 1970-01-01
    • 1970-01-01
    • 2018-03-26
    • 1970-01-01
    • 2018-09-11
    相关资源
    最近更新 更多