【问题标题】:Converting Wide Format Data into Long Format with Multiple Indices and Grouped Data将宽格式数据转换为具有多个索引和分组数据的长格式
【发布时间】:2020-07-22 22:34:02
【问题描述】:

我有一个宽格式的数据框:

import pandas as pd
df = pd.DataFrame({'time': [1, 2, 3], 
                   'factor': ['a','a','b'],
                   'variable1': [0,0,0],
                   'variable2': [0,0,1],
                   'variable3': [0,2,0],
                   'variable4': [2,0,1],
                   'variable5': [1,0,1],
                   'variable6': [0,1,1],                   
                   'O1V1': [0,0.2,-0.3],
                   'O1V2': [0,0.4,-0.9],
                   'O1V3': [0.5,0.2,-0.6],
                   'O1V4': [0.5,0.2,-0.6],
                   'O1V5': [0,0.2,-0.3],
                   'O1V6': [0,0.4,-0.9],
                   'O1V7': [0.5,0.2,-0.6],
                   'O1V8': [0.5,0.2,-0.6],                   
                   'O2V1': [0,0.5,0.3],
                   'O2V2': [0,0.2,0.9],
                   'O2V3': [0.6,0.1,-0.3],
                   'O2V4': [0.5,0.2,-0.6],
                   'O2V5': [0,0.5,0.3],
                   'O2V6': [0,0.2,0.9],
                   'O2V7': [0.6,0.1,-0.3],
                   'O2V8': [0.5,0.2,-0.6],                   
                   'O3V1': [0,0.7,0.4],
                   'O3V2': [0.9,0.2,-0.3],
                   'O3V3': [0.5,0.2,-0.7],
                   'O3V4': [0.5,0.2,-0.6],
                   'O3V5': [0,0.7,0.4],
                   'O3V6': [0.9,0.2,-0.3],
                   'O3V7': [0.5,0.2,-0.7],
                   'O3V8': [0.5,0.2,-0.6]})

数据框的每一行代表一个时间段。有多个“对象”被监测,即 O1、O2 和 O3。每个受试者有 8 个变量被测量。我需要将此数据转换为长格式,其中每一行包含一个主题在给定时间段的信息,但只有前 4 个主题变量,以及第 2-4 列中有关此时间段的额外信息,但是不是第 5-8 列。

最终输出应如下所示:

df_final = pd.DataFrame({'time': [1, 2, 3, 1, 2, 3, 1, 2, 3], 
                   'factor': ['a','a','b','a','a','b','a','a','b'],
                   'variable1': [0,0,0,0,0,0,0,0,0],
                   'variable2': [0,0,1,0,0,1,0,0,1],                 
                   'id': [1,1,1,2,2,2,3,3,3],
                   'V1': [0,0.2,-0.3,0,0.5,0.3,0,0.7,0.4],
                   'V2': [0,0.4,-0.9,0,0.2,0.9,0.9,0.2,-0.3],
                   'V3': [0.5,0.2,-0.6,0.6,0.1,-0.3,0.5,0.2,-0.7],
                   'V4': [0.5,0.2,-0.6,0.5,0.2,-0.6,0.5,0.2,-0.6]})

我可以使用如下的 for 循环来实现这一点(此代码按时间而不是 id 对数据进行排序,但按 id 排序不是必需的):

import numpy as np

#make every 8 columns of first row into its own row
long = np.array(df.iloc[0,:]).reshape(-1,8)

#make array of numbers 1-3 (I'm not an experienced python programmer, 
#so I suspect that this is a very verbose way of achieving this)
array = np.arange(3)
array = array.reshape(3,1)
array+=1

#concatenate first 4 columns of first row with first four columns of every other row, adding index from array variable
long = np.concatenate([np.tile(long[0,:4].reshape(-1,4),(3,1)),array,long[1:,:4]],axis=1) 

#repeat this process for each object id and concatenate
for i in [1,2]:
    temp = np.array(df.iloc[i,:]).reshape(-1,8)    
    temp = np.concatenate([np.tile(temp[0,:4].reshape(-1,4),(3,1)),array,temp[1:,:4]],axis=1) 
    long = np.concatenate([long,temp])

这个方法达到了预期的效果,但是我有问题:

  1. 此方法依赖于在主题变量出现之前有 8 个变量的事实,从而允许 .reshape (-1,8) 行起作用。我正在尝试找到一种不管非主题变量的数量如何都可以工作的方法。

  2. 这个解决方案中的 for 循环似乎是可以避免的。我曾尝试寻找利用 NumPy 函数来实现此目的的方法,但没有找到任何方法,或者至少不明白如何像这样使用它们。我知道我可以编写自己的函数并将其应用于每一行,但是我特别希望了解如何使用典型的 Python 包,因为我是 Python 新手。

【问题讨论】:

    标签: python pandas numpy formatting


    【解决方案1】:

    使用wide_to_long。要么一开始就删除你不需要的列,要么重塑所有内容,然后在之后进行子集化:我们需要颠倒一些列名,因为'O3V6'应该采用'V6O3'的形式才能使存根工作(在这里我们将其设为“V63”,因此 id 前面没有 O)。

    df = df.rename(columns={x: x[2:]+x[1:2] for x in df.columns[df.columns.str.startswith('O')]})
    
    df1 = pd.wide_to_long(df, i=['time', 'factor']+[f'variable{i}' for i in range(1,7)], 
                          j='id', stubnames=[f'V{i}' for i in range(1,9)], suffix='.*')
    
    df1 = (df1.reset_index()
              .drop(columns=[f'V{i}' for i in range(5,9)]
                            +[f'variable{i}' for i in range(3,7)]))
    

       time factor  variable1  variable2  id   V1   V2   V3   V4
    0     1      a          0          0   1  0.0  0.0  0.5  0.5
    1     1      a          0          0   2  0.0  0.0  0.6  0.5
    2     1      a          0          0   3  0.0  0.9  0.5  0.5
    3     2      a          0          0   1  0.2  0.4  0.2  0.2
    4     2      a          0          0   2  0.5  0.2  0.1  0.2
    5     2      a          0          0   3  0.7  0.2  0.2  0.2
    6     3      b          0          1   1 -0.3 -0.9 -0.6 -0.6
    7     3      b          0          1   2  0.3  0.9 -0.3 -0.6
    8     3      b          0          1   3  0.4 -0.3 -0.7 -0.6
    

    【讨论】:

    • 感谢您的回答。我喜欢这个答案,但输出与所需的输出不同。有没有办法将它转换成每一行都显示时间、因子、变量 1 和变量 2?
    • 我使用 David Erickson 的回答中的一行来实现这一点:df1 = df1.reset_index().sort_values(['id','time'])
    【解决方案2】:

    将第一列设置为索引:

    cols = df.columns[~df.columns.str.contains("O\dV\d")]
    df = df.set_index(cols.tolist())
    

    从剩余的列创建多索引 - 我们将 V 之前的数字分开:

    df.columns = pd.MultiIndex.from_tuples([(int(col[1:2]), col[2:]) 
                                             for col in df.columns
                                            ], 
                                             names = ['id', None]
                                           )
    

    现在,我们堆叠获取 id 列,删除我们不感兴趣的列和索引以获得最终输出:

    (df.stack(0)
     .iloc[:, :4]
     .sort_index(level="id")
     .droplevel([4, 5, 6, 7])
     .reset_index()
      )
    

    【讨论】:

      【解决方案3】:

      这将给出准确的输出:

      1. 从列名中删除O + 数字,只得到V 和数字
      2. .melt 将数据帧转换成更长的格式
      3. 创建一个id 列,该列将找到具有.groupby.cumcount()+1 的相关组。
      4. 将所有非值列设置为索引,并使用.unstack(4)将索引中的第五列作为标题,将数据帧转换为接近您正在寻找的格式。
      5. 进行一些最后的清理/格式化以将数据转换为完美的格式。

      代码:

      df.columns = df.columns.str.replace('O[0-9]', '', regex=True)
      cols = ['time', 'factor', 'variable1', 'variable2']
      df = df.melt(id_vars=cols, value_vars=['V1','V2','V3','V4'])
      df['id'] = df.groupby(cols + ['variable']).cumcount()+1
      df = df.set_index(cols + ['id','variable']).unstack(5)
      df.columns = df.columns.droplevel(0)
      df = df.reset_index().sort_values(['id','time'])
      df.columns.name = None
      df
      

      输出:

          time factor variable1 variable2 id  V1   V2   V3    V4
      0   1    a      0         0         1   0.0  0.0  0.5   0.5
      3   2    a      0         0         1   0.2  0.4  0.2   0.2
      6   3    b      0         1         1   -0.3 -0.9 -0.6  -0.6
      1   1    a      0         0         2   0.0  0.0  0.6   0.5
      4   2    a      0         0         2   0.5  0.2  0.1   0.2
      7   3    b      0         1         2   0.3  0.9  -0.3  -0.6
      2   1    a      0         0         3   0.0  0.9  0.5   0.5
      5   2    a      0         0         3   0.7  0.2  0.2   0.2
      8   3    b      0         1         3   0.4  -0.3 -0.7  -0.6
      

      【讨论】:

        猜你喜欢
        • 1970-01-01
        • 1970-01-01
        • 1970-01-01
        • 2020-11-28
        • 2023-02-25
        • 2014-06-07
        • 1970-01-01
        • 1970-01-01
        • 1970-01-01
        相关资源
        最近更新 更多