如何融化熊猫数据框?
- 2024-11-20 08:43:00
- admin 原创
- 6
问题描述:
在熊猫标签,我经常看到用户询问有关在 Pandas 中融合数据框的问题。我将尝试针对此主题进行规范问答(自答)。
我要澄清的是:
什么是熔化?
如何使用 melt?
何时使用 melt?
我看到一些关于 melt 的热门问题,例如:
使用 Pandas 将列转换为行:这个实际上可能不错,但如果有更多的解释会更好。
Pandas Melt 函数:一个很好的问题,答案也很好,但是有点太模糊,没有太多解释。
融化熊猫数据框:也是一个不错的答案!但这仅适用于那种非常简单的特定情况,仅
pd.melt(df)
Pandas 数据框使用列作为行 (melt):非常简洁!但问题是,它只针对 OP 提出的特定问题,这也是需要使用的
pivot_table
。
因此我将尝试针对该主题进行规范的问答。
数据集:
我的所有答案都将基于这个包含随机年龄、随机人群的随机成绩的数据集(这样更容易解释答案:D):
import pandas as pd
df = pd.DataFrame({'Name': ['Bob', 'John', 'Foo', 'Bar', 'Alex', 'Tom'],
'Math': ['A+', 'B', 'A', 'F', 'D', 'C'],
'English': ['C', 'B', 'B', 'A+', 'F', 'A'],
'Age': [13, 16, 16, 15, 15, 13]})
>>> df
Name Math English Age
0 Bob A+ C 13
1 John B B 16
2 Foo A B 16
3 Bar F A+ 15
4 Alex D F 15
5 Tom C A 13
问题:
问题 1:
如何融化数据框以便原始数据框变成以下内容?
Name Age Subject Grade
0 Bob 13 English C
1 John 16 English B
2 Foo 16 English B
3 Bar 15 English A+
4 Alex 17 English F
5 Tom 12 English A
6 Bob 13 Math A+
7 John 16 Math B
8 Foo 16 Math A
9 Bar 15 Math F
10 Alex 17 Math D
11 Tom 12 Math C
我想将其转置,以便一列是每个科目,而其他列是学生的重复姓名以及他们的年龄和分数。
问题2:
这和问题 1 类似,但这次我想让问题 1 输出的Subject
列只有Math
,我想过滤掉该English
列:
Name Age Subject Grades
0 Bob 13 Math A+
1 John 16 Math B
2 Foo 16 Math A
3 Bar 15 Math F
4 Alex 15 Math D
5 Tom 13 Math C
我希望输出像上面那样。
问题3:
如果我要对融化进行分组并根据学生的分数对他们进行排序,我该怎么做才能获得如下所示的所需输出:
value Name Subjects
0 A Foo, Tom Math, English
1 A+ Bob, Bar Math, English
2 B John, John, Foo Math, English, English
3 C Tom, Bob Math, English
4 D Alex Math
5 F Bar, Alex Math, English
我需要对其进行排序,并且名称分别用逗号分隔,并且Subjects
以相同的顺序用逗号分隔。
问题4:
我该如何取消融化的数据框?假设我已经融化了这个数据框:
df = df.melt(id_vars=['Name', 'Age'], var_name='Subject', value_name='Grades')
成为:
Name Age Subject Grades
0 Bob 13 Math A+
1 John 16 Math B
2 Foo 16 Math A
3 Bar 15 Math F
4 Alex 15 Math D
5 Tom 13 Math C
6 Bob 13 English C
7 John 16 English B
8 Foo 16 English B
9 Bar 15 English A+
10 Alex 15 English F
11 Tom 13 English A
那么我该如何将其转换回下面的原始数据框?
Name Math English Age
0 Bob A+ C 13
1 John B B 16
2 Foo A B 16
3 Bar F A+ 15
4 Alex D F 15
5 Tom C A 13
问题5:
如果我要按学生姓名分组,并用逗号分隔科目和成绩,我该怎么做?
Name Subject Grades
0 Alex Math, English D, F
1 Bar Math, English F, A+
2 Bob Math, English A+, C
3 Foo Math, English A, B
4 John Math, English B, B
5 Tom Math, English C, A
我想要一个像上面那样的数据框。
问题6:
如果我要完全融化我的数据框,所有列作为值,我该怎么做?
Column Value
0 Name Bob
1 Name John
2 Name Foo
3 Name Bar
4 Name Alex
5 Name Tom
6 Math A+
7 Math B
8 Math A
9 Math F
10 Math D
11 Math C
12 English C
13 English B
14 English B
15 English A+
16 English F
17 English A
18 Age 13
19 Age 16
20 Age 16
21 Age 15
22 Age 15
23 Age 13
我想要一个像上面那样的数据框。所有列都是值。
解决方案 1:
对于 pandas 版本 < 0.20.0 的注意事项:我将使用它df.melt(...)
作为示例,但是您需要使用pd.melt(df, ...)
它。
文档参考:
这里的大多数解决方案都将使用melt
,因此要了解方法melt
,请参阅文档说明。
将 DataFrame 从宽格式转换为长格式,可选择保留标识符设置。
此函数可用于将 DataFrame 转换为一种格式,其中一列或多列是标识符变量(id_vars),而所有其他列(被视为测量变量(value_vars))都“不旋转”到行轴,只留下两个非标识符列“变量”和“值”。
参数
id_vars:元组、列表或 ndarray,可选
用作标识符变量的列。
value_vars:元组、列表或 ndarray,可选
要取消透视的列。如果未指定,则使用所有未设置为 id_vars 的列。
变量名称:标量
用于“变量”列的名称。如果为 None,则使用 frame.columns.name 或“变量”。
value_name:标量,默认“value”
用于“值”列的名称。
col_level:int 或 str,可选
如果列是多索引,则使用此级别进行融化。
ignore_index:bool,默认 True
如果为 True,则忽略原始索引。如果为 False,则保留原始索引。索引标签将根据需要重复。
1.1.0 版本中的新功能。
熔化逻辑:
Melting 合并多列并将数据框从宽转换为长,对于问题 1 的解决方案(见下文),步骤如下:
首先我们得到原始数据框。
然后,融化首先合并
Math
和English
列并使数据框复制(更长)。Subject
最后,它分别添加作为列值主题的列Grades
:
这就是该函数执行的简单逻辑melt
。
解决方案:
问题 1:
问题 1 可以pd.DataFrame.melt
通过以下代码解决:
print(df.melt(id_vars=['Name', 'Age'], var_name='Subject', value_name='Grades'))
此代码将id_vars
参数传递给['Name', 'Age']
,然后自动value_vars
将设置为其他列(['Math', 'English']
),并将其转置为该格式。
您还可以使用stack
以下方法解决问题 1:
print(
df.set_index(["Name", "Age"])
.stack()
.reset_index(name="Grade")
.rename(columns={"level_2": "Subject"})
.sort_values("Subject")
.reset_index(drop=True)
)
此代码将Name
和Age
列设置为索引并堆叠其余列Math
和English
,并重置索引并指定Grade
为列名,然后将另一列重命名level_2
为Subject
,然后按Subject
列排序,最后再次重置索引。
这两种解决方案都输出:
Name Age Subject Grade
0 Bob 13 English C
1 John 16 English B
2 Foo 16 English B
3 Bar 15 English A+
4 Alex 17 English F
5 Tom 12 English A
6 Bob 13 Math A+
7 John 16 Math B
8 Foo 16 Math A
9 Bar 15 Math F
10 Alex 17 Math D
11 Tom 12 Math C
问题2:
这和我的第一个问题类似,但是这个问题我只需要在Math
列中进行过滤,这时候value_vars
参数就可以派上用场了,如下所示:
print(
df.melt(
id_vars=["Name", "Age"],
value_vars="Math",
var_name="Subject",
value_name="Grades",
)
)
或者我们也可以使用stack
列规范:
print(
df.set_index(["Name", "Age"])[["Math"]]
.stack()
.reset_index(name="Grade")
.rename(columns={"level_2": "Subject"})
.sort_values("Subject")
.reset_index(drop=True)
)
这两种解决方案都给出了:
Name Age Subject Grade
0 Bob 13 Math A+
1 John 16 Math B
2 Foo 16 Math A
3 Bar 15 Math F
4 Alex 15 Math D
5 Tom 13 Math C
问题3:
melt
问题 3 可以通过和来解决groupby
,使用agg
函数', '.join
,如下所示:
print(
df.melt(id_vars=["Name", "Age"])
.groupby("value", as_index=False)
.agg(", ".join)
)
它融化数据框,然后按等级分组并聚合它们,并用逗号连接它们。
stack
也可以用来解决这个问题,stack
如下groupby
所示:
print(
df.set_index(["Name", "Age"])
.stack()
.reset_index()
.rename(columns={"level_2": "Subjects", 0: "Grade"})
.groupby("Grade", as_index=False)
.agg(", ".join)
)
此stack
函数只是以相当于的方式转置数据框melt
,然后重置索引,重命名列和组并聚合。
两种解决方案都输出:
Grade Name Subjects
0 A Foo, Tom Math, English
1 A+ Bob, Bar Math, English
2 B John, John, Foo Math, English, English
3 C Bob, Tom English, Math
4 D Alex Math
5 F Bar, Alex Math, English
问题4:
我该如何取消融化的数据框?假设我已经融化了这个数据框:
df = df.melt(id_vars=['Name', 'Age'], var_name='Subject', value_name='Grades')
这可以用 来解决。pivot_table
我们必须指定参数values
、index
以及columns
。aggfunc
我们可以用下面的代码解决这个问题:
print(
df.pivot_table("Grades", ["Name", "Age"], "Subject", aggfunc="first")
.reset_index()
.rename_axis(columns=None)
)
输出:
Name Age English Math
0 Alex 15 F D
1 Bar 15 A+ F
2 Bob 13 C A+
3 Foo 16 B A
4 John 16 B B
5 Tom 13 A C
融合的数据框将转换回与原始数据框完全相同的格式。
我们首先旋转融化的数据框,然后重置索引并删除列轴名称。
问题5:
melt
问题 5 可以通过groupby
以下方式解决:
print(
df.melt(id_vars=["Name", "Age"], var_name="Subject", value_name="Grades")
.groupby("Name", as_index=False)
.agg(", ".join)
)
融化并聚集Name
。
或者你可以stack
:
print(
df.set_index(["Name", "Age"])
.stack()
.reset_index()
.groupby("Name", as_index=False)
.agg(", ".join)
.rename({"level_2": "Subjects", 0: "Grades"}, axis=1)
)
两个代码都输出:
Name Subjects Grades
0 Alex Math, English D, F
1 Bar Math, English F, A+
2 Bob Math, English A+, C
3 Foo Math, English A, B
4 John Math, English B, B
5 Tom Math, English C, A
问题6:
问题 6 可以通过以下方式解决melt
,无需指定任何列,只需指定预期的列名:
print(df.melt(var_name='Column', value_name='Value'))
这会融化整个数据框。
或者你可以stack
:
print(
df.stack()
.reset_index(level=1)
.sort_values("level_1")
.reset_index(drop=True)
.set_axis(["Column", "Value"], axis=1)
)
两个代码都输出:
Column Value
0 Age 16
1 Age 15
2 Age 15
3 Age 16
4 Age 13
5 Age 13
6 English A+
7 English B
8 English B
9 English A
10 English F
11 English C
12 Math C
13 Math A+
14 Math D
15 Math B
16 Math F
17 Math A
18 Name Alex
19 Name Bar
20 Name Tom
21 Name Foo
22 Name John
23 Name Bob
解决方案 2:
还有一种melt
问题没有提到,那就是当数据框的列标题包含公共前缀时,你想将后缀融合到列值中。
这与“如何旋转数据框?”中的问题 11有点相反。
假设您有以下 DataFrame,并且您想要将其融合1970
为1980
列值
A1970 A1980 B1970 B1980 X id
0 a d 2.5 3.2 -1.085631 0
1 b e 1.2 1.3 0.997345 1
2 c f 0.7 0.1 0.282978 2
在这种情况下你可以尝试pandas.wide_to_long
pd.wide_to_long(df, stubnames=["A", "B"], i="id", j="year")
X A B
id year
0 1970 -1.085631 a 2.5
1 1970 0.997345 b 1.2
2 1970 0.282978 c 0.7
0 1980 -1.085631 d 3.2
1 1980 0.997345 e 1.3
2 1980 0.282978 f 0.1
解决方案 3:
根据U12-Forward 的描述,melt
对数据框进行整形主要意味着将数据从宽格式转换为长格式。与原始数据框相比,新数据框的行数更多,列数更少。
说到融合,有不同的场景——所有列标签可以融合为一列或多列;列标签的某些部分可以保留为标题,而其余部分则整理为一列,等等。这个答案展示了如何使用pyjanitor的pd.stack
、pd.melt
和pivot_longerpd.wide_to_long
来融合 pandas 数据框(我是 pyjanitor 库的贡献者)。这些例子并不详尽,但希望在将数据框从宽格式重塑为长格式时,它们能为您指明正确的方向。
示例数据
df = pd.DataFrame(
{'Sepal.Length': [5.1, 5.9],
'Sepal.Width': [3.5, 3.0],
'Petal.Length': [1.4, 5.1],
'Petal.Width': [0.2, 1.8],
'Species': ['setosa', 'virginica']}
)
df
Sepal.Length Sepal.Width Petal.Length Petal.Width Species
0 5.1 3.5 1.4 0.2 setosa
1 5.9 3.0 5.1 1.8 virginica
场景 1- 融化所有柱子:
在本例中,我们希望将所有指定的列标题转换为行 - 这可以使用pd.melt
或来完成pd.stack
,问题 1 的解决方案已经涵盖了这一点。 重塑也可以使用pivot_longer
# pip install pyjanitor
import janitor
df.pivot_longer(index = 'Species')
Species variable value
0 setosa Sepal.Length 5.1
1 virginica Sepal.Length 5.9
2 setosa Sepal.Width 3.5
3 virginica Sepal.Width 3.0
4 setosa Petal.Length 1.4
5 virginica Petal.Length 5.1
6 setosa Petal.Width 0.2
7 virginica Petal.Width 1.8
就像在 中一样pd.melt
,您可以通过将参数传递给 和参数来重命名variable
和列:value
`names_to`values_to
df.pivot_longer(index = 'Species',
names_to = 'dimension',
values_to = 'measurement_in_cm')
Species dimension measurement_in_cm
0 setosa Sepal.Length 5.1
1 virginica Sepal.Length 5.9
2 setosa Sepal.Width 3.5
3 virginica Sepal.Width 3.0
4 setosa Petal.Length 1.4
5 virginica Petal.Length 5.1
6 setosa Petal.Width 0.2
7 virginica Petal.Width 1.8
您还可以保留原始索引,并根据出现顺序保留数据框:
df.pivot_longer(index = 'Species',
names_to = 'dimension',
values_to = 'measurement_in_cm',
ignore_index = False,
sort_by_appearance=True)
Species dimension measurement_in_cm
0 setosa Sepal.Length 5.1
0 setosa Sepal.Width 3.5
0 setosa Petal.Length 1.4
0 setosa Petal.Width 0.2
1 virginica Sepal.Length 5.9
1 virginica Sepal.Width 3.0
1 virginica Petal.Length 5.1
1 virginica Petal.Width 1.8
默认情况下,中的值names_to
是字符串;它们可以通过参数转换为其他数据类型names_transform
- 这对于大型数据框很有帮助/性能更高,因为与重塑后转换数据类型相比,它通常更有效率。
out = df.pivot_longer(index = 'Species',
names_to = 'dimension',
values_to = 'measurement_in_cm',
ignore_index = False,
sort_by_appearance=True,
names_transform = 'category')
out.dtypes
Species object
dimension category
measurement_in_cm float64
dtype: object
场景 2 - 将列标签融合到多个列中:
到目前为止,我们已将数据融合到单个列中,一个用于列名,一个用于值。但是,在某些情况下,我们希望将列标签拆分为不同的列,甚至将值拆分为不同的列。继续使用我们的示例数据,我们可能希望将sepal
和petal
放在一part
列下,而将length
和width
放在一dimension
列中:
通过
pd.melt
-熔化后进行分离:
out = df.melt(id_vars = 'Species')
arr = out.variable.str.split('.')
(out
.assign(part = arr.str[0],
dimension = arr.str[1])
.drop(columns = 'variable')
)
Species value part dimension
0 setosa 5.1 Sepal Length
1 virginica 5.9 Sepal Length
2 setosa 3.5 Sepal Width
3 virginica 3.0 Sepal Width
4 setosa 1.4 Petal Length
5 virginica 5.1 Petal Length
6 setosa 0.2 Petal Width
7 virginica 1.8 Petal Width
Via-
pd.stack
提供一种更有效的列分割方法;分割是在列上进行的,这意味着随着数据量的增加,需要处理的行数更少,意味着结果可能会更快:
out = df.set_index('Species')
# This returns a MultiIndex
out.columns = out.columns.str.split('.', expand = True)
new_names = ['part', 'dimension']
out.columns.names = new_names
out.stack(new_names).rename('value').reset_index()
Species part dimension value
0 setosa Petal Length 1.4
1 setosa Petal Width 0.2
2 setosa Sepal Length 5.1
3 setosa Sepal Width 3.5
4 virginica Petal Length 5.1
5 virginica Petal Width 1.8
6 virginica Sepal Length 5.9
7 virginica Sepal Width 3.0
Via
pivot_longer
- 需要注意的关键pivot_longer
是它会寻找模式。列标签用点分隔.
。只需将新名称的列表/元组传递给names_to
,并将分隔符传递给names_sep
(在底层它只使用pd.str.split
):
df.pivot_longer(index = 'Species',
names_to = ('part', 'dimension'),
names_sep='.')
Species part dimension value
0 setosa Sepal Length 5.1
1 virginica Sepal Length 5.9
2 setosa Sepal Width 3.5
3 virginica Sepal Width 3.0
4 setosa Petal Length 1.4
5 virginica Petal Length 5.1
6 setosa Petal Width 0.2
7 virginica Petal Width 1.8
到目前为止,我们已经了解了 melt、stack 和 pivot_longer 如何将列标签拆分为多个新列,只要有定义的分隔符即可。如果没有明确定义的分隔符,比如下面的数据框,该怎么办?
# https://github.com/tidyverse/tidyr/blob/main/data-raw/who.csv
who = pd.DataFrame({'id': [1], 'new_sp_m5564': [2], 'newrel_f65': [3]})
who
id new_sp_m5564 newrel_f65
0 1 2 3
在第二列中,我们有多个_
,而第三列只有一个_
。这里的目标是将列标签拆分为单独的列(sp
&rel
到diagnosis
列,m
&f
到gender
列,数字到age
列)。一种选择是通过正则表达式提取列子标签。
通过
pd.melt
- 再次使用,熔化后pd.melt
发生重塑:
out = who.melt('id')
regex = r"new_?(?P<diagnosis>.+)_(?P<gender>.)(?P<age>d+)"
new_df = out.variable.str.extract(regex)
# pd.concat can be used here instead
out.drop(columns='variable').assign(**new_df)
id value diagnosis gender age
0 1 2 sp m 5564
1 1 3 rel f 65
请注意正则表达式的摘录是如何成组出现的(括号中的)。
通过
pd.stack
- 就像前面的例子一样,拆分是在列上完成的,在效率方面提供了更多:
out = who.set_index('id')
regex = r"new_?(.+)_(.)(d+)"
new_names = ['diagnosis', 'age', 'gender']
# Returns a dataframe
new_cols = out.columns.str.extract(regex)
new_cols.columns = new_names
new_cols = pd.MultiIndex.from_frame(new_cols)
out.columns = new_cols
out.stack(new_names).rename('value').reset_index()
id diagnosis age gender value
0 1 rel f 65 3.0
1 1 sp m 5564 2.0
再次,提取出成组的正则表达式。
通过
pivot_longer
- 我们再次知道模式和新的列名,我们只需将它们传递给函数,这次我们使用names_pattern
,因为我们正在处理正则表达式。摘录将匹配组中的正则表达式(括号中的组):
regex = r"new_?(.+)_(.)(d+)"
new_names = ['diagnosis', 'age', 'gender']
who.pivot_longer(index = 'id',
names_to = new_names,
names_pattern = regex)
id diagnosis age gender value
0 1 sp m 5564 2
1 1 rel f 65 3
场景 3 - 将列标签和值融合到多个列中:
如果我们还想将值拆分成多个列怎么办?让我们使用SO 上一个相当流行的问题:
df = pd.DataFrame({'City': ['Houston', 'Austin', 'Hoover'],
'State': ['Texas', 'Texas', 'Alabama'],
'Name':['Aria', 'Penelope', 'Niko'],
'Mango':[4, 10, 90],
'Orange': [10, 8, 14],
'Watermelon':[40, 99, 43],
'Gin':[16, 200, 34],
'Vodka':[20, 33, 18]},
columns=['City', 'State', 'Name', 'Mango', 'Orange', 'Watermelon', 'Gin', 'Vodka'])
df
City State Name Mango Orange Watermelon Gin Vodka
0 Houston Texas Aria 4 10 40 16 20
1 Austin Texas Penelope 10 8 99 200 33
2 Hoover Alabama Niko 90 14 43 34 18
目标是将Mango
、Orange
和Watermelon
整理成水果列,Gin
将和Vodka
整理Drinks
成列,并将相应的值分别整理成Pounds
和Ounces
。
通过
pd.melt
-我正在逐字复制优秀的解决方案:
df1 = df.melt(id_vars=['City', 'State'],
value_vars=['Mango', 'Orange', 'Watermelon'],
var_name='Fruit', value_name='Pounds')
df2 = df.melt(id_vars=['City', 'State'],
value_vars=['Gin', 'Vodka'],
var_name='Drink', value_name='Ounces')
df1 = df1.set_index(['City', 'State', df1.groupby(['City', 'State']).cumcount()])
df2 = df2.set_index(['City', 'State', df2.groupby(['City', 'State']).cumcount()])
df3 = (pd.concat([df1, df2],axis=1)
.sort_index(level=2)
.reset_index(level=2, drop=True)
.reset_index())
print (df3)
City State Fruit Pounds Drink Ounces
0 Austin Texas Mango 10 Gin 200.0
1 Hoover Alabama Mango 90 Gin 34.0
2 Houston Texas Mango 4 Gin 16.0
3 Austin Texas Orange 8 Vodka 33.0
4 Hoover Alabama Orange 14 Vodka 18.0
5 Houston Texas Orange 10 Vodka 20.0
6 Austin Texas Watermelon 99 NaN NaN
7 Hoover Alabama Watermelon 43 NaN NaN
8 Houston Texas Watermelon 40 NaN NaN
通过
pd.stack
- 我想不出通过堆栈的解决方案,所以我会跳过通过
pivot_longer
- 通过将名称列表传递给names_to
和values_to
,并将正则表达式列表传递给 ,可以有效地完成重塑 - 当将值拆分为多列时,需要names_pattern
一个正则表达式列表:names_pattern
df.pivot_longer(
index=["City", "State"],
column_names=slice("Mango", "Vodka"),
names_to=("Fruit", "Drink"),
values_to=("Pounds", "Ounces"),
names_pattern=[r"M|O|W", r"G|V"],
)
City State Fruit Pounds Drink Ounces
0 Houston Texas Mango 4 Gin 16.0
1 Austin Texas Mango 10 Gin 200.0
2 Hoover Alabama Mango 90 Gin 34.0
3 Houston Texas Orange 10 Vodka 20.0
4 Austin Texas Orange 8 Vodka 33.0
5 Hoover Alabama Orange 14 Vodka 18.0
6 Houston Texas Watermelon 40 None NaN
7 Austin Texas Watermelon 99 None NaN
8 Hoover Alabama Watermelon 43 None NaN
随着数据帧大小的增加,效率会更高。
场景 4 - 将相似的列组合在一起:
扩展融合到多列的概念,假设我们希望将相似的列组合在一起。我们不关心保留列标签,只是将相似列的值组合成新列。
df = pd.DataFrame({'x_1_mean': [10],
'x_2_mean': [20],
'y_1_mean': [30],
'y_2_mean': [40],
'unit': [50]})
df
x_1_mean x_2_mean y_1_mean y_2_mean unit
0 10 20 30 40 50
对于上述代码,我们希望将相似的列(以相同字母开头的列)组合成新的唯一列 - 所有x*
列将归入x_mean
,而所有y*
列将归入y_mean
。我们不保存列标签,我们只对这些列的值感兴趣:
通过 pd.melt - 通过 melt 的一种可能方法是通过 groupby 在列上运行它:
out = df.set_index('unit')
grouped = out.columns.str.split('_d_').str.join('')
# group on the split
grouped = out.groupby(grouped, axis = 1)
# iterate, melt individually, and recombine to get a new dataframe
out = {key : frame.melt(ignore_index = False).value
for key, frame in grouped}
pd.DataFrame(out).reset_index()
unit xmean ymean
0 50 10 30
1 50 20 40
通过 pd.stack - 在这里我们拆分列并构建一个多重索引:
out = df.set_index('unit')
split = out.columns.str.split('_(d)_')
split = [(f"{first}{last}", middle)
for first, middle, last
in split]
out.columns = pd.MultiIndex.from_tuples(split)
out.stack(-1).droplevel(-1).reset_index()
unit xmean ymean
0 50 10 30
1 50 20 40
通过 pd.wide_to_long - 在这里我们重新排序子标签 - 将数字移动到列的末尾:
out = df.set_index('unit')
out.columns = [f"{first}{last}_{middle}"
for first, middle, last
in out.columns.str.split('_(d)_')]
(pd
.wide_to_long(
out.reset_index(),
stubnames = ['xmean', 'ymean'],
i = 'unit',
j = 'num',
sep = '_')
.droplevel(-1)
.reset_index()
)
unit xmean ymean
0 50 10 30
1 50 20 40
通过pivot_longer - 同样,对于
pivot_longer
,一切都与模式有关。只需将新列名列表传递给names_to
,并将相应的正则表达式传递给names_pattern
:
df.pivot_longer(index = 'unit',
names_to = ['xmean', 'ymean'],
names_pattern = ['x', 'y']
)
unit xmean ymean
0 50 10 30
1 50 20 40
请注意,此模式遵循先到先得的原则 - 如果列顺序颠倒,pivot_longer
则会产生不同的输出。让我们看看实际效果:
# reorder the columns in a different form:
df = df.loc[:, ['x_1_mean', 'x_2_mean', 'y_2_mean', 'y_1_mean', 'unit']]
df
x_1_mean x_2_mean y_2_mean y_1_mean unit
0 10 20 40 30 50
由于顺序已改变,x_1_mean
将与 配对y_2_mean
,因为这是它看到的第一y
列,而x_2_mean
与 配对y_1_mean
:
df.pivot_longer(index = 'unit',
names_to = ['xmean', 'ymean'],
names_pattern = ['x', 'y']
)
unit xmean ymean
0 50 10 40
1 50 20 30
注意输出与上一次运行的区别。这是在将 names_pattern 与序列结合使用时需要注意的一点。顺序很重要。
场景 5 - 保留部分列名作为标题:
这可能是重塑为长格式时最大的用例之一。我们可能希望将列标签的某些部分保留为标题,并将其余列移动到新列(甚至忽略它们)。
让我们重新审视一下鸢尾花数据框:
df = pd.DataFrame(
{'Sepal.Length': [5.1, 5.9],
'Sepal.Width': [3.5, 3.0],
'Petal.Length': [1.4, 5.1],
'Petal.Width': [0.2, 1.8],
'Species': ['setosa', 'virginica']}
)
df
Sepal.Length Sepal.Width Petal.Length Petal.Width Species
0 5.1 3.5 1.4 0.2 setosa
1 5.9 3.0 5.1 1.8 virginica
我们的目标是保留作为列名Sepal
,Petal
并将其余的(Length
,Width
)整理到一dimension
列中:
通过 pd.melt -融化成长格式后使用枢轴:
out = df.melt(id_vars = 'Species')
arr = out.variable.str.split('.')
(out
.assign(part = arr.str[0],
dimension = arr.str[1])
.pivot(['Species', 'dimension'], 'part', 'value')
.rename_axis(columns = None)
.reset_index()
)
Species dimension Petal Sepal
0 setosa Length 1.4 5.1
1 setosa Width 0.2 3.5
2 virginica Length 5.1 5.9
3 virginica Width 1.8 3.0
这不如下面的其他选项那么有效,因为这涉及到从宽到长,然后从长到宽,这在足够大的数据框上可能会性能不佳。
通过 pd.stack - 这提供了更高的效率,因为大多数重塑都是在列上进行的 - 少即是多。
out = df.set_index('Species')
out.columns = out.columns.str.split('.', expand = True)
out.columns.names = [None, 'dimension']
out.stack('dimension').reset_index()
Species dimension Petal Sepal
0 setosa Length 1.4 5.1
1 setosa Width 0.2 3.5
2 virginica Length 5.1 5.9
3 virginica Width 1.8 3.0
通过 pd.wide_to_long - 简单直接 - 只需传入相关参数:
(pd
.wide_to_long(
df,
stubnames=['Sepal', 'Petal'],
i = 'Species',
j = 'dimension',
sep='.',
suffix='.+')
.reset_index()
)
Species dimension Sepal Petal
0 setosa Length 5.1 1.4
1 virginica Length 5.9 5.1
2 setosa Width 3.5 0.2
3 virginica Width 3.0 1.8
随着数据大小的增加,pd.wide_to_long
效率可能会降低。
通过pivot_longer:再次回到模式。由于我们将列的一部分保留为标题,因此我们使用
.value
作为占位符。该函数看到.value
并知道该子标签必须保留为标题。列中的拆分可以是names_sep
或names_pattern
。在这种情况下,使用更简单names_sep
:
df.pivot_longer(index = 'Species',
names_to = ('.value', 'dimension'),
names_sep = '.')
Species dimension Sepal Petal
0 setosa Length 5.1 1.4
1 virginica Length 5.9 5.1
2 setosa Width 3.5 0.2
3 virginica Width 3.0 1.8
当用 分割列时.
,我们有Petal, Length
。当与 相比时('.value', 'dimension')
,Petal
与 相关联.value
,而Length
与 相关联dimension
。Petal
保留为列标题,而Length
集中到dimension
列中。我们不需要明确列名,我们只需使用.value
并让函数完成繁重的工作。这样,如果您有很多列,您就不需要确定哪些列应该保留为标题,只要您通过names_sep
或获得正确的模式即可names_pattern
。
如果我们想要将Length
/Width
作为列名,并将Petal/Sepal
其集中到一part
列中,该怎么办:
通过 pd.melt
out = df.melt(id_vars = 'Species')
arr = out.variable.str.split('.')
(out
.assign(part = arr.str[0],
dimension = arr.str[1])
.pivot(['Species', 'part'], 'dimension', 'value')
.rename_axis(columns = None)
.reset_index()
)
Species part Length Width
0 setosa Petal 1.4 0.2
1 setosa Sepal 5.1 3.5
2 virginica Petal 5.1 1.8
3 virginica Sepal 5.9 3.0
通过pd.stack:
out = df.set_index('Species')
out.columns = out.columns.str.split('.', expand = True)
out.columns.names = ['part', None]
out.stack('part').reset_index()
Species part Length Width
0 setosa Petal 1.4 0.2
1 setosa Sepal 5.1 3.5
2 virginica Petal 5.1 1.8
3 virginica Sepal 5.9 3.0
通过 pd.wide_to_long - 首先,我们需要重新排列列,使得
Length
/Width
位于前面:
out = df.set_index('Species')
out.columns = out.columns.str.split('.').str[::-1].str.join('.')
(pd
.wide_to_long(
out.reset_index(),
stubnames=['Length', 'Width'],
i = 'Species',
j = 'part',
sep='.',
suffix='.+')
.reset_index()
)
Species part Length Width
0 setosa Sepal 5.1 3.5
1 virginica Sepal 5.9 3.0
2 setosa Petal 1.4 0.2
3 virginica Petal 5.1 1.8
通过pivot_longer:
df.pivot_longer(index = 'Species',
names_to = ('part', '.value'),
names_sep = '.')
Species part Length Width
0 setosa Sepal 5.1 3.5
1 virginica Sepal 5.9 3.0
2 setosa Petal 1.4 0.2
3 virginica Petal 5.1 1.8
请注意,我们不必对列进行任何重新排序(有些情况下,重新排序是不可避免的),该函数只需.value
与拆分结果配对names_sep
,然后输出重新整形的数据框。您甚至可以.value
在适用的情况下使用多个。让我们重新回顾一下早期的数据框:
df = pd.DataFrame({'x_1_mean': [10],
'x_2_mean': [20],
'y_1_mean': [30],
'y_2_mean': [40],
'unit': [50]})
df
x_1_mean x_2_mean y_1_mean y_2_mean unit
0 10 20 30 40 50
df.pivot_longer(index = 'unit',
names_to = ('.value', '.value'),
names_pattern = r"(.).+(mean)")
unit xmean ymean
0 50 10 30
1 50 20 40
这一切都是为了发现模式并利用它们。pivot_longer
只是为常见的重塑场景提供了高效且高性能的抽象 - 而底层只是 Pandas、NumPy和 Python。
希望当您需要从宽到长重塑时,各种答案能够为您指明正确的方向。
- 2024年20款好用的项目管理软件推荐,项目管理提效的20个工具和技巧
- 2024年开源项目管理软件有哪些?推荐5款好用的项目管理工具
- 项目管理软件有哪些?推荐7款超好用的项目管理工具
- 项目管理软件哪个最好用?盘点推荐5款好用的项目管理工具
- 项目管理软件有哪些最好用?推荐6款好用的项目管理工具
- 项目管理软件有哪些,盘点推荐国内外超好用的7款项目管理工具
- 2024项目管理软件排行榜(10类常用的项目管理工具全推荐)
- 项目管理软件排行榜:2024年项目经理必备5款开源项目管理软件汇总
- 2024年常用的项目管理软件有哪些?推荐这10款国内外好用的项目管理工具
- 项目管理必备:盘点2024年13款好用的项目管理软件