首页 > 解决方案 > pandas groupby 索引:nan 冲突或只取值

问题描述

我正在处理一些数据,pandas这些数据可能会有一些我需要处理的不一致之处。数据是值的时间序列:

                     A     B      C
YYYY MM DD hh mm                                
2017 8  20 23 0      1   2.0    NaN
              10     2   4.0    NaN
              20     3   6.0    NaN
              30     4   8.0    NaN
...                ...   ...    ...
2019 6  4  10 10   100   100    NaN
              20   200   102    NaN
              30   300   104    NaN
              40   400   106    NaN
              50   500   108      0

数据可能存在的不一致是同一["YYYY", "MM", "DD", "hh", "mm"]索引的多个条目。在大多数情况下,当时的值是相同的,因此我可以使用df.drop_duplicates(keep="first")删除具有相同索引和列值的所有行。

但是,存在索引冲突,其中值不相同或非 nan 值仅存在于其中一行中。我追求的行为是:

对于重复索引,基于每列:

  1. 如果只有一个非 nan 值:使用它。
  2. 如果所有 nan 值:使用 nan。
  3. 如果所有相同的(非nan)值:使用它。
  4. 如果不相等(non-nan)值:使用 nan。

例如(是一个简化的 DataFrame):

     A      B    C
0    1    2.0  NaN
1    2    NaN  NaN
1  100  500.0  NaN
2    3    6.0  NaN
2  200    6.0  NaN
3  300    8.0  NaN
3  300    NaN  5.0
3  300    NaN  NaN
4  400  106.0  NaN

应该导致:

       A      B    C
0    1.0    2.0  NaN
1    NaN  500.0  NaN
2    NaN    6.0  NaN
3  300.0    8.0  5.0
4  400.0  106.0  NaN

我试图用几种方法解决这个问题,但它们在数据集大小上都非常慢。

当前缓慢的解决方案(您可能需要滚动代码片段窗口):

import numpy as np
import pandas as pd

df = pd.DataFrame(
    [
        dict(A=1, B=2.0, C=None),
        dict(A=2, B=None, C=None),
        dict(A=100, B=500, C=None),
        dict(A=3, B=6.0, C=None),
        dict(A=200, B=6.0, C=None),
        dict(A=300, B=8.0, C=None),
        dict(A=300, B=None, C=5.0),
        dict(A=300, B=None, C=None),
        dict(A=400, B=106, C=None),
    ],
    index=[0, 1, 1, 2, 2, 3, 3, 3, 4],
)


# SLOW SOLUTION 1
def canonical(colum_values):
    candidates = colum_values.dropna().unique()
    if len(candidates) == 1:
        return candidates[0]
    else:
        return np.nan


solution_1 = df.groupby(df.index).aggregate(canonical)


# SLOW & UGLY SOLUTION 2
def solve_2(df):
    df = df.copy()

    for dupe in df.index[df.index.duplicated(keep="first")]:
        for column in df.columns:
            values = df[df.index == dupe][column]

            if len(values.dropna().unique()) == 1:
                df.loc[df.index == dupe, column] = values.dropna().iloc[0]
            else:
                df.loc[df.index == dupe, column] = np.nan
    
    # duplicate rows should all now share the same value, so just keep one.
    df.drop_duplicates(keep="first", inplace=True)

    return df


solution_2 = solve_2(df)

寻找对这些的任何改进,以在大型数据集上获得更好的性能。

谢谢。

标签: pythonpandasperformance

解决方案


您可以group使用数据框level=0和聚合.first来获取每组的第一个non-nan值,level=0然后.mask是每组有多个唯一值的列中的值level=0

g = df.groupby(level=0)
g.first().mask(g.nunique().gt(1))

细节:

g.first()

     A      B    C
0    1    2.0  NaN
1    2  500.0  NaN
2    3    6.0  NaN
3  300    8.0  5.0
4  400  106.0  NaN

g.nunique().gt(1)

       A      B      C
0  False  False  False
1   True  False  False
2   True  False  False
3  False  False  False
4  False  False  False

结果:

       A      B    C
0    1.0    2.0  NaN
1    NaN  500.0  NaN
2    NaN    6.0  NaN
3  300.0    8.0  5.0
4  400.0  106.0  NaN

推荐阅读