Pandas - 将多列旋转为更少的列,并保留一定程度的细节

Pandas - pivoting multiple columns into fewer columns with some level of detail kept

假设我有以下生成数据帧的代码:

df = pd.DataFrame({"customer_code": ['1234','3411','9303'],
                   "main_purchases": [3,10,5],
                   "main_revenue": [103.5,401.5,99.0],
                   "secondary_purchases": [1,2,4],
                   "secondary_revenue": [43.1,77.5,104.6]
                  })

df.head()

customer_code 列是每个客户的唯一 ID。

然后有 2 列表示发生的购买以及这些客户从主要分支机构产生的收入。

另外 2 列表示这些客户来自二级分支机构的 purchases/revenue。

我想将数据转换成这样的格式,其中有一个新的列来区分主要和次要的列,但收入数字和购买列没有混淆:

显而易见的解决方案是将其拆分为 2 个数据帧,然后简单地进行连接,但我想知道是否有内置的方法可以在一行或两行中执行此操作 - 这让我印象深刻有些人可能会想出解决方案。

首先使用正则表达式重命名一个小列以获取列名称中的“收入”和“购买”,然后str.replace we can use pd.wide_to_long将这些现在的存根名称从列转换为行:

# Reorder column names so stubnames are first
df.columns = [df.columns[0],
              *df.columns[1:].str.replace(r'(.*)_(.*)', r'_', regex=True)]

# Convert wide_to_long
df = (
    pd.wide_to_long(
        df,
        i='customer_code',
        stubnames=['purchases', 'revenue'],
        j='type',
        sep='_',
        suffix='.*'
    )
        .sort_index()  # Optional sort to match expected output
        .reset_index()  # retrieve customer_code from the index
)

df:

customer_code type purchases revenue
0 1234 main 3 103.5
1 1234 secondary 1 43.1
2 3411 main 10 401.5
3 3411 secondary 2 77.5
4 9303 main 5 99
5 9303 secondary 4 104.6

重新排序列 header 有什么作用?

df.columns = [df.columns[0],
              *df.columns[1:].str.replace(r'(.*)_(.*)', r'_', regex=True)]

生产:

Index(['customer_code', 'purchases_main', 'revenue_main',
       'purchases_secondary', 'revenue_secondary'],
      dtype='object')

“类型”列现在是 header 列的后缀,它允许 wide_to_long 按预期处理 table。

您可以使用 pivot_longer from pyjanitor 抽象重塑过程;它们只是 Pandas:

中的一堆包装函数
#pip install pyjanitor
import pandas as pd
import janitor
df.pivot_longer(index = 'customer_code',
                names_to=('type', '.value'), 
                names_sep='_', 
                sort_by_appearance=True)
 
  customer_code       type  purchases  revenue
0          1234       main          3    103.5
1          1234  secondary          1     43.1
2          3411       main         10    401.5
3          3411  secondary          2     77.5
4          9303       main          5     99.0
5          9303  secondary          4    104.6

names_to 中的 .value 向函数表示您希望列的那部分保留为 header;另一部分在 type 列下。在这种情况下,拆分由 names_sep 确定(有一个 names_pattern 选项,允许正则表达式拆分);如果不在意出现顺序,可以设置sort_by_appearance为False。

您也可以使用 melt() 和 concat() 函数来解决这个问题。

import pandas as pd

df1 = df.melt(
          id_vars='customer_code',
          value_vars=['main_purchases', 'secondary_purchases'],
          var_name='type',
          value_name='purchases',
          ignore_index=True)

df2 = df.melt(
          id_vars='customer_code',
          value_vars=['main_revenue', 'secondary_revenue'],
          var_name='type',
          value_name='revenue',
          ignore_index=True)

然后我们使用参数axis=1的concat()并排连接并使用sort_values(by='customer_code')按客户排序数据

result= pd.concat([df1,df2['revenue']], 
               axis=1,
               ignore_index=False).sort_values(by='customer_code')

使用带有正则表达式的 replace() 来对齐类型名称:

result.type.replace(r'_.*$','', regex=True, inplace=True)

以上代码将输出以下数据框:

customer_code type purchases revenue
0 1234 main 3 103.5
3 1234 secondary 1 43.1
1 3411 main 10 401.5
4 3411 secondary 2 77.5
2 9303 main 5 99
5 9303 secondary 4 104.6