【问题标题】:Pandas - Reshape a dataframe columns based on repeated key-value pair columns with duplicate namesPandas - 根据具有重复名称的重复键值对列重塑数据框列
【发布时间】:2021-05-30 19:55:45
【问题描述】:

我从 API 接收数据帧,其中包含初始列中某些实体的数据,而后面的列包含这些实体的元数据。数据是专有的,所以我无法准确显示,但数据框的结构如下所示:

idx  name types  value value_date        desc group owner key_name key_value   key_name   key_value   key_name  key_value
  1 name1 type1     45 2021-05-30 name1-type1    G1    O1       k1        A1         k2          A2         k3         A3
  2 name1 type2     23 2021-05-30 name1-type2    G1    O1       k3        B3        NaN         NaN         k2         B2
  3 name2 type1     41 2021-05-30 name2-type1    G2    O2      NaN       NaN         k1          C1        NaN        NaN
  4 name3 type1     39 2021-05-30 name3-type1    G1    O1       k1        D1         k3          D3        NaN        NaN
  5 name4 type1     40 2021-05-30 name4-type1    G3    O3       k1        E1         k3          E3         k2         E2
  6 name4 type2     21 2021-05-30 name4-type2    G3    O3       k3        F3         k2          F2         k1         F1
  7 name4 type3     11 2021-05-30 name4-type3    G3    O3      NaN       NaN        NaN         NaN        NaN        NaN
  8 name5 type1     44 2021-05-30 name5-type1    G1    O1      NaN       NaN         k1          H1        NaN        NaN
  9 name6 type1     49 2021-05-30 name6-type1    G2    O2      NaN       NaN         k2          I2        NaN        NaN
 10 name6 type2     26 2021-05-30 name6-type2    G2    O2       k1        J1        NaN         NaN         k3         J3

以下代码将生成上述示例数据框:

df = pd.DataFrame( {'idx': {0: 1, 1: 2, 2: 3, 3: 4, 4: 5, 5: 6, 6: 7, 7: 8, 8: 9, 9: 10}, 'name': {0: 'name1', 1: 'name1', 2: 'name2', 3: 'name3', 4: 'name4', 5: 'name4', 6: 'name4', 7: 'name5', 8: 'name6', 9: 'name6'}, 'types': {0: 'type1', 1: 'type2', 2: 'type1', 3: 'type1', 4: 'type1', 5: 'type2', 6: 'type3', 7: 'type1', 8: 'type1', 9: 'type2'}, 'value': {0: 45, 1: 23, 2: 41, 3: 39, 4: 40, 5: 21, 6: 11, 7: 44, 8: 49, 9: 26}, 'value_date': {0: '2021-05-30', 1: '2021-05-30', 2: '2021-05-30', 3: '2021-05-30', 4: '2021-05-30', 5: '2021-05-30', 6: '2021-05-30', 7: '2021-05-30', 8: '2021-05-30', 9: '2021-05-30'}, 'desc': {0: 'name1-type1', 1: 'name1-type2', 2: 'name2-type1', 3: 'name3-type1', 4: 'name4-type1', 5: 'name4-type2', 6: 'name4-type3', 7: 'name5-type1', 8: 'name6-type1', 9: 'name6-type2'}, 'group': {0: 'G1', 1: 'G1', 2: 'G2', 3: 'G1', 4: 'G3', 5: 'G3', 6: 'G3', 7: 'G1', 8: 'G2', 9: 'G2'}, 'owner': {0: 'O1', 1: 'O1', 2: 'O2', 3: 'O1', 4: 'O3', 5: 'O3', 6: 'O3', 7: 'O1', 8: 'O2', 9: 'O2'}, 'key_name': {0: 'k1', 1: 'k3', 2: float('nan'), 3: 'k1', 4: 'k1', 5: 'k3', 6: float('nan'), 7: float('nan'), 8: float('nan'), 9: 'k1'}, 'key_value': {0: 'A1', 1: 'B3', 2: float('nan'), 3: 'D1', 4: 'E1', 5: 'F3', 6: float('nan'), 7: float('nan'), 8: float('nan'), 9: 'J1'}, 'key_name_1': {0: 'k2', 1: float('nan'), 2: 'k1', 3: 'k3', 4: 'k3', 5: 'k2', 6: float('nan'), 7: 'k1', 8: 'k2', 9: float('nan')}, 'key_value_1': {0: 'A2', 1: float('nan'), 2: 'C1', 3: 'D3', 4: 'E3', 5: 'F2', 6: float('nan'), 7: 'H1', 8: 'I2', 9: float('nan')}, 'key_name_2': {0: 'k3', 1: 'k2', 2: float('nan'), 3: float('nan'), 4: 'k2', 5: 'k1', 6: float('nan'), 7: float('nan'), 8: float('nan'), 9: 'k3'}, 'key_value_2': {0: 'A3', 1: 'B2', 2: float('nan'), 3: float('nan'), 4: 'E2', 5: 'F1', 6: float('nan'), 7: float('nan'), 8: float('nan'), 9: 'J3'}} )
df.rename(columns={'key_name_1':'key_name','key_value_1':'key_value','key_name_2':'key_name','key_value_2':'key_value'}, inplace=True)

注意重复 "key_name", "key_value" 对列在 owner 之后。这些列保存元数据值,因此key_name 是元数据名称,key_value 是它的值。现在一个实体可以有不同数量的元数据,所以对于某些行,可能有 15-20 个这样的对列。

如果元数据不适用于该实体,则其他行将具有 NaN

我想将此数据框重塑为以下数据框,其中每个 key_name 成为列,并且该列中的值应该是每个实体的 key_value(如果适用):

idx    name   types   value value_date        desc   group   owner      k1      k2      k3
  1   name1   type1      45 2021-05-30 name1-type1      G1      O1      A1      A2      A3
  2   name1   type2      23 2021-05-30 name1-type2      G1      O1     NaN      B2      B3
  3   name2   type1      41 2021-05-30 name2-type1      G2      O2      C1     NaN     NaN
  4   name3   type1      39 2021-05-30 name3-type1      G1      O1      D1     NaN      D3
  5   name4   type1      40 2021-05-30 name4-type1      G3      O3      E1      E2      E3
  6   name4   type2      21 2021-05-30 name4-type2      G3      O3      F1      F2      F3
  7   name4   type3      11 2021-05-30 name4-type3      G3      O3     NaN     NaN     NaN
  8   name5   type1      44 2021-05-30 name5-type1      G1      O1      H1     NaN     NaN
  9   name6   type1      49 2021-05-30 name6-type1      G2      O2     NaN      I2     NaN
 10   name6   type2      26 2021-05-30 name6-type2      G2      O2      J1     NaN      J3

现在的问题是这些 "key_name", "key_value" 对数据对于问题数据框中的每个实体的顺序不同。如果您看到 name1-type1(k1, A1) 按顺序首先出现,然后是 (k2, A2), .... 但是对于 name4-type2(k3, F3) 首先出现,然后是 k2 strong> 然后是 k1,完全相反。

我怀疑 API 中发生的情况是,对于每个唯一实体,数据都在循环中获取,然后稍后连接,因此连接有效,但元数据的顺序并不能保证。我无法修复 API,因为它不是我的,所以我需要修复它的输出才能继续。

【问题讨论】:

    标签: python pandas dataframe


    【解决方案1】:

    pyjanitor 的帮助工具组合可能会有所帮助:

    # pip install pyjanitor
    import janitor
    import pandas as pd
    (df.pivot_longer(index = slice("idx", "owner"), 
                     names_to=("names", "values"), 
                     names_pattern=["name", "value"], 
                     values_to="valued")
      .dropna()
      .pivot_wider(index = slice("idx", "owner"), 
                   names_from="names")
      .merge(df.loc[:, "idx":"owner"], 
             how = 'outer')
      .sort_values('idx')
      )
    
        idx   name  types  value  value_date         desc group owner   k1   k3   k2
    0    1  name1  type1     45  2021-05-30  name1-type1    G1    O1   A1   A3   A2
    1    2  name1  type2     23  2021-05-30  name1-type2    G1    O1  NaN   B3   B2
    2    3  name2  type1     41  2021-05-30  name2-type1    G2    O2   C1  NaN  NaN
    3    4  name3  type1     39  2021-05-30  name3-type1    G1    O1   D1   D3  NaN
    4    5  name4  type1     40  2021-05-30  name4-type1    G3    O3   E1   E3   E2
    5    6  name4  type2     21  2021-05-30  name4-type2    G3    O3   F1   F3   F2
    9    7  name4  type3     11  2021-05-30  name4-type3    G3    O3  NaN  NaN  NaN
    6    8  name5  type1     44  2021-05-30  name5-type1    G1    O1   H1  NaN  NaN
    7    9  name6  type1     49  2021-05-30  name6-type1    G2    O2  NaN  NaN   I2
    8   10  name6  type2     26  2021-05-30  name6-type2    G2    O2   J1   J3  NaN
    

    pivot_longer 只是提供了一种从宽到长重塑的更简单方法 - 对于您的情况,有一个模式(一些列有 name,一些列有 .value) - 我们在 names_pattern 中使用该模式来翻转桌子。 pivot_wider 建立在 pandas pivot 之上,在这种情况下只是语法糖——这里使用的唯一原因是因为我不想输入所有索引名称 :),但你可以跳过它,直接使用 pivot .结果不包括idx 7,因为它完全为空……merging 回到原来的df 重新引入它。

    他们只是帮手,所以我们可以不包括他们。这是实现此目的的一种可能方法:

    熔化列,保留原始索引:

    index = [*df.columns[:8]]
    
    base = (df.melt(index, value_name = "valued", ignore_index = False)
              .dropna()
              .drop(columns="variable"))
    

    提取valued 仅以K 开头的数据框,同时将index 附加到现有索引:

    Ks = (base.loc[base.valued.str.startswith("k")]
              .set_index([*index], append = True)
          )
    

    提取valued 不以K 开头的数据框:

    non_Ks = (base.loc[~base.valued.str.startswith("k")]
                  .rename(columns={"valued":"flip_this_column"})
                  .set_index(index, append = True)
               )
    

    结合Ksnon_Ks,去掉不相关的标签并重置索引:

    (pd.concat([Ks, non_Ks], axis = 'columns')
       .set_index('valued',append=True)
       .unstack()
       .droplevel(0, axis = 'columns')
       .rename_axis(columns=None)
       .reset_index(index)
    )
    
    
    
    idx   name  types  value  value_date         desc group owner   k1   k2   k3
    0    1  name1  type1     45  2021-05-30  name1-type1    G1    O1   A1   A2   A3
    1    2  name1  type2     23  2021-05-30  name1-type2    G1    O1  NaN   B2   B3
    2    3  name2  type1     41  2021-05-30  name2-type1    G2    O2   C1  NaN  NaN
    3    4  name3  type1     39  2021-05-30  name3-type1    G1    O1   D1  NaN   D3
    4    5  name4  type1     40  2021-05-30  name4-type1    G3    O3   E1   E2   E3
    5    6  name4  type2     21  2021-05-30  name4-type2    G3    O3   F1   F2   F3
    7    8  name5  type1     44  2021-05-30  name5-type1    G1    O1   H1  NaN  NaN
    8    9  name6  type1     49  2021-05-30  name6-type1    G2    O2  NaN   I2  NaN
    9   10  name6  type2     26  2021-05-30  name6-type2    G2    O2   J1  NaN   J3
    

    【讨论】:

    • 感谢pyjanitor!。不知道这些。可能不会采用您为实现此目的而提供的第二种方式(尽管它很容易阅读:)),因为它使用[*df.columns[:8]]startswith("k")。有问题的数据框是一个示例数据框,原始数据框有更多以k 开头的数据列。但是您提供的第一个解决方案效果很好!
    【解决方案2】:

    步骤:

    1. 过滤那些以keyidx 开头的列。
    2. set_index('idx') 使用reshapekey and values 组合在一起。
    3. 分解数据框并使用pd.Serieskeyvalue 展开到不同的列中。
    4. 使用pivot 重构数据框。
    5. 最后与原版合并
    df1 = df.filter(regex=r'idx|key*')
    df2 = df1.set_index('idx').apply(lambda x: x.dropna(
    ).values.reshape(-1, 2), axis=1).explode().dropna().apply(pd.Series).reset_index()
    df2 = df2.pivot(*df2).reset_index()
    df = df[[col for col in df.columns if not col.startswith('key')]].merge(
        df2, on='idx', how='left')
    

    输出

       idx   name  types  value  value_date         desc group owner   k1   k2  \
    0    1  name1  type1     45  2021-05-30  name1-type1    G1    O1   A1   A2   
    1    2  name1  type2     23  2021-05-30  name1-type2    G1    O1  NaN   B2   
    2    3  name2  type1     41  2021-05-30  name2-type1    G2    O2   C1  NaN   
    3    4  name3  type1     39  2021-05-30  name3-type1    G1    O1   D1  NaN   
    4    5  name4  type1     40  2021-05-30  name4-type1    G3    O3   E1   E2   
    5    6  name4  type2     21  2021-05-30  name4-type2    G3    O3   F1   F2   
    6    7  name4  type3     11  2021-05-30  name4-type3    G3    O3  NaN  NaN   
    7    8  name5  type1     44  2021-05-30  name5-type1    G1    O1   H1  NaN   
    8    9  name6  type1     49  2021-05-30  name6-type1    G2    O2  NaN   I2   
    9   10  name6  type2     26  2021-05-30  name6-type2    G2    O2   J1  NaN   
    
        k3  
    0   A3  
    1   B3  
    2  NaN  
    3   D3  
    4   E3  
    5   F3  
    6  NaN  
    7  NaN  
    8  NaN  
    9   J3  
    

    【讨论】:

    • 工作,谢谢。一个问题,df2.pivot(*df2) 部分究竟是如何工作的。我的意思是我知道枢轴是如何工作的,但从未见过它像这样实现,所以有点困惑:)
    • @Ank 如果值的顺序是 -> 'index' / 'column' / 'value' 那么我们可以简单地使用 *df 来代替 pivot 中指定的关键字 args第一列并将其用作`索引`/将使用第二列作为column,第三列作为value
    猜你喜欢
    • 2012-10-09
    • 1970-01-01
    • 1970-01-01
    • 1970-01-01
    • 1970-01-01
    • 1970-01-01
    • 2021-12-06
    • 2019-09-26
    • 2019-03-07
    相关资源
    最近更新 更多