【问题标题】:Setting varying columns for a subset of rows in a pandas multiindex dataframe为 pandas 多索引数据框中的行子集设置不同的列
【发布时间】:2018-05-14 19:16:44
【问题描述】:

我想将大型 pandas 数据帧 df 的特定行和不同的多索引列中的值重新分配给非 NaN 值,这些值已计算并存储在数据帧的稍小掩码子集 df_sub 中。

df =
    A                                                           B        
      0     1     2     3     4     5     6     7     8     9      0     1     2     3     4     5     6     7     8     9        
0   1.0   2.0   3.0   4.0   5.0   6.0   7.0   8.0   9.0  10.0  -51.0 -50.0 -49.0 -48.0 -47.0 -46.0 -45.0 -44.0 -43.0 -42.0   
1  11.0  12.0  13.0  14.0  15.0  16.0  17.0  18.0  19.0  20.0  -41.0 -40.0 -39.0 -38.0 -37.0 -36.0 -35.0 -34.0 -33.0 -32.0   
2  21.0  22.0  23.0  24.0  25.0  26.0  27.0  28.0  29.0  30.0  -31.0 -30.0 -29.0 -28.0 -27.0 -26.0 -25.0 -24.0 -23.0 -22.0   
3  31.0  32.0  33.0  34.0  35.0  36.0  37.0  38.0  39.0  40.0  -21.0 -20.0 -29.0 -28.0 -27.0 -26.0 -25.0 -24.0 -23.0 -22.0   
4  41.0  42.0  43.0  44.0  45.0  46.0  47.0  48.0  49.0  50.0  -11.0 -10.0  -9.0  -8.0  -7.0  -6.0  -5.0  -4.0  -3.0  -2.0  

df_sub =
      0     1     2     3     4     5     6     7     8     9 
1    NaN   NaN   NaN   NaN   NaN   0.3   0.2   0.1   NaN   NaN
3    NaN   NaN   NaN   0.6   0.9   0.7   NaN   NaN   NaN   NaN

我的目标是获得 df.loc[:,'B'] 的结果,如下所示,其中 df_sub 中的非 NaN 值替换了 df (i.e., df.loc[1, pd.IndexSlice['B', 5:7]] = df_sub.loc[1, 5:7] and df.loc[3, pd.IndexSlice['B', 3:5]] = df_sub.loc[3, 3:5]) 的相应行和列:

df.loc[:,'B'] =
      0     1     2     3     4     5     6     7     8     9
0 -51.0 -50.0 -49.0 -48.0 -47.0 -46.0 -45.0 -44.0 -43.0 -42.0
1 -41.0 -40.0 -39.0 -38.0 -37.0   0.3   0.2   0.1 -33.0 -32.0
2 -31.0 -30.0 -29.0 -28.0 -27.0 -26.0 -25.0 -24.0 -23.0 -22.0
3 -21.0 -20.0 -19.0   0.6   0.9   0.7 -15.0 -14.0 -13.0 -12.0
4 -11.0 -10.0  -9.0  -8.0  -7.0  -6.0  -5.0  -4.0  -3.0  -2.0

但是,我得到的是 NaN,而不是想要的值:

df.loc[:,'B'] =
      0     1     2     3     4     5     6     7     8     9
0 -51.0 -50.0 -49.0 -48.0 -47.0 -46.0 -45.0 -44.0 -43.0 -42.0
1 -41.0 -40.0 -39.0 -38.0 -37.0   NaN   NaN   NaN -33.0 -32.0
2 -31.0 -30.0 -29.0 -28.0 -27.0 -26.0 -25.0 -24.0 -23.0 -22.0
3 -21.0 -20.0 -19.0   NaN   NaN   NaN -15.0 -14.0 -13.0 -12.0
4 -11.0 -10.0  -9.0  -8.0  -7.0  -6.0  -5.0  -4.0  -3.0  -2.0

我的简单示例代码如下。从诊断结果来看,一切都按预期运行:1) 为 df_sub 的每一行标识了来自 df_sub 的非 nan 值及其索引,2) 原始 df 的切片似乎是正确的,以及 3)分配是在没有投诉或“设置副本”警告的情况下进行的。

  1. 实现我的目标的适当方法是什么?
  2. 为什么会失败?
  3. 是否有更紧凑、更高效的方式来执行任务?

简化示例:

# Create data for example case
idf = pd.MultiIndex.from_product([['A', 'B'], np.arange(0,10)])
df = pd.DataFrame(np.concatenate((np.arange(1.,51.).reshape(5,10), 
                  np.arange(-51., -1.).reshape(5,10)), axis=1), 
                  index=np.arange(0,5), columns=idf)
df_sub = pd.DataFrame([[np.nan, np.nan, np.nan, np.nan, np.nan, 0.5, 0.6, 0.7, np.nan, np.nan], 
                      [np.nan, np.nan, np.nan, 0.3, 0.4, 0.5, np.nan, np.nan, np.nan, np.nan]],
                      index=[1,3], columns=np.arange(0,10))
dfsub_idx = df_sub.index

# Perform assignments
for (idx, row) in df_sub.iterrows() :
   arr = row.index[~row.isnull()] 
   print 'row {}: \n{}'.format(idx, row)
   print 'non-nan indices: {}\n'.format(arr)
   print 'df before mod: \n{}'.format(df.loc[idx, pd.IndexSlice['B', arr.tolist()]])
   df.loc[idx, pd.IndexSlice['B', arr.tolist()]] = row[arr] 
   print 'df after mod: \n{}'.format(df.loc[idx, pd.IndexSlice['B', arr.tolist()]])

【问题讨论】:

    标签: python pandas dataframe assign


    【解决方案1】:

    pandas.DataFrame.alignpandas.DataFrame.fillna 内联

    通过使用level 参数

    pd.DataFrame.fillna(*df_sub.align(df, level=1))
    
          A                                                           B                                                      
          0     1     2     3     4     5     6     7     8     9     0     1     2     3     4     5     6     7     8     9
    0   1.0   2.0   3.0   4.0   5.0   6.0   7.0   8.0   9.0  10.0 -51.0 -50.0 -49.0 -48.0 -47.0 -46.0 -45.0 -44.0 -43.0 -42.0
    1  11.0  12.0  13.0  14.0  15.0   0.5   0.6   0.7  19.0  20.0 -41.0 -40.0 -39.0 -38.0 -37.0   0.5   0.6   0.7 -33.0 -32.0
    2  21.0  22.0  23.0  24.0  25.0  26.0  27.0  28.0  29.0  30.0 -31.0 -30.0 -29.0 -28.0 -27.0 -26.0 -25.0 -24.0 -23.0 -22.0
    3  31.0  32.0  33.0   0.3   0.4   0.5  37.0  38.0  39.0  40.0 -21.0 -20.0 -19.0   0.3   0.4   0.5 -15.0 -14.0 -13.0 -12.0
    4  41.0  42.0  43.0  44.0  45.0  46.0  47.0  48.0  49.0  50.0 -11.0 -10.0  -9.0  -8.0  -7.0  -6.0  -5.0  -4.0  -3.0  -2.0
    

    使用update

    df.update(df_sub.align(df, level=1)[0])
    

    澄清

    这个:

    pd.DataFrame.fillna(*df_sub.align(df, level=1))
    

    相当于

    a, b = df_sub.align(df, level=1)
    a.fillna(b)
    # Or pd.DataFrame.fillna(a, b)
    

    【讨论】:

    • 它被称为 splat。这与拆包有关。 align 返回的是两个值的元组。通过 splat 继续它,我告诉 Python 将元组中的这两个元素作为前两个参数传递给 fillna 函数。我将编辑帖子以澄清。
    • 在我意识到你已经回答了我之前,我不小心删除了我的问题,所以我发布了我最初的问题:Genuis!谢谢!不过,我还有一个问题。我试图了解 fillna() 函数中参数前面的星号 (*) 发生了什么。我尝试在网上搜索,但到目前为止还没有找到好的解释。您能否解释一下或提供可能对我有帮助的链接?感谢您的澄清。
    • 其实上面的方案影响了df['A'] AND df['B']的结果,这里我只想改df['B']。在我的实际情况中,我有 7 个类别(例如,df['A'] 到 df['G')] 每个类别都有 39 个子级别,所以我宁愿能够将 df_sub 与 df['B'] 对齐,然后更新 df['B']。如果没有收到“SettingWithCopyError”,我仍然没有成功完成此操作。我已经尝试使用 inplace=True 的 update() 以及执行类似 df.loc[:, 'B'] 的操作,但无济于事。进一步的建议将不胜感激。谢谢。
    • 我也没有提到实际上,对于我的特殊情况,我在 df 中有 134550 行,而 df_sub 是原始 df 的 1051 行的修改副本(例如,df.loc[选定的行 idx, pdIndexSlice['B', :]])。因此,复制整个数据帧可能是不可取的。
    • 没关系....想通了。 df['B'] = pd.DataFrame.fillna(*df_sub.align(df['B'], level=1)) 可以解决问题。感谢您的洞察力....非常有帮助....并且学到了一些新东西!
    【解决方案2】:

    你应该在df_sub的末尾添加values.iloc

    df.loc[1, pd.IndexSlice['B', 5:7]] = df_sub.loc[1, 5:7].values 
    df.loc[3, pd.IndexSlice['B', 3:5]] = df_sub.loc[3, 3:5].values
    

    【讨论】:

    • 不错的答案 (-:
    猜你喜欢
    • 2014-10-05
    • 2021-12-31
    • 2016-09-25
    • 2017-02-06
    • 2015-03-16
    • 2017-11-29
    • 2021-01-17
    • 2016-05-21
    • 2020-07-22
    相关资源
    最近更新 更多