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
假设我有以下生成数据帧的代码:
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 |