如何融化熊猫数据框?

2024-11-20 08:43:00
admin
原创
6
摘要:问题描述:在熊猫标签,我经常看到用户询问有关在 Pandas 中融合数据框的问题。我将尝试针对此主题进行规范问答(自答)。我要澄清的是:什么是熔化?如何使用 melt?何时使用 melt?我看到一些关于 melt 的热门问题,例如:使用 Pandas 将列转换为行:这个实际上可能不错,但如果有更多的解释会更好...

问题描述:

在熊猫标签,我经常看到用户询问有关在 Pandas 中融合数据框的问题。我将尝试针对此主题进行规范问答(自答)。

我要澄清的是:

  1. 什么是熔化?

  2. 如何使用 melt?

  3. 何时使用 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_levelint 或 str,可选

如果列是多索引,则使用此级别进行融化。

  • ignore_indexbool,默认 True

如果为 True,则忽略原始索引。如果为 False,则保留原始索引。索引标签将根据需要重复。

1.1.0 版本中的新功能。

熔化逻辑:

Melting 合并多列并将数据框从宽转换为长,对于问题 1 的解决方案(见下文),步骤如下:

  1. 首先我们得到原始数据框。

  2. 然后,融化首先合并MathEnglish列并使数据框复制(更长)。

  3. Subject最后,它分别添加作为列值主题的列Grades

IT科技

这就是该函数执行的简单逻辑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)
)

此代码将NameAge列设置为索引并堆叠其余列MathEnglish,并重置索引并指定Grade为列名,然后将另一列重命名level_2Subject,然后按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我们必须指定参数valuesindex以及columnsaggfunc

我们可以用下面的代码解决这个问题:

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,并且您想要将其融合19701980列值

  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.stackpd.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 - 将列标签融合到多个列中:

到目前为止,我们已将数据融合到单个列中,一个用于列名,一个用于值。但是,在某些情况下,我们希望将列标签拆分为不同的列,甚至将值拆分为不同的列。继续使用我们的示例数据,我们可能希望将sepalpetal放在一part列下,而将lengthwidth放在一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&reldiagnosis列,m&fgender列,数字到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

目标是将MangoOrangeWatermelon整理成水果列,Gin将和Vodka整理Drinks成列,并将相应的值分别整理成PoundsOunces

  • 通过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_tovalues_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

我们的目标是保留作为列名SepalPetal并将其余的(LengthWidth)整理到一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_sepnames_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与 相关联dimensionPetal保留为列标题,而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。

希望当您需要从宽到长重塑时,各种答案能够为您指明正确的方向。

相关推荐
  为什么项目管理通常仍然耗时且低效?您是否还在反复更新电子表格、淹没在便利贴中并参加每周更新会议?这确实是耗费时间和精力。借助软件工具的帮助,您可以一目了然地全面了解您的项目。如今,国内外有足够多优秀的项目管理软件可以帮助您掌控每个项目。什么是项目管理软件?项目管理软件是广泛行业用于项目规划、资源分配和调度的软件。它使项...
项目管理软件   601  
  华为IPD与传统研发模式的8大差异在快速变化的商业环境中,产品研发模式的选择直接决定了企业的市场响应速度和竞争力。华为作为全球领先的通信技术解决方案供应商,其成功在很大程度上得益于对产品研发模式的持续创新。华为引入并深度定制的集成产品开发(IPD)体系,相较于传统的研发模式,展现出了显著的差异和优势。本文将详细探讨华为...
IPD流程是谁发明的   7  
  如何通过IPD流程缩短产品上市时间?在快速变化的市场环境中,产品上市时间成为企业竞争力的关键因素之一。集成产品开发(IPD, Integrated Product Development)作为一种先进的产品研发管理方法,通过其结构化的流程设计和跨部门协作机制,显著缩短了产品上市时间,提高了市场响应速度。本文将深入探讨如...
华为IPD流程   9  
  在项目管理领域,IPD(Integrated Product Development,集成产品开发)流程图是连接创意、设计与市场成功的桥梁。它不仅是一个视觉工具,更是一种战略思维方式的体现,帮助团队高效协同,确保产品按时、按质、按量推向市场。尽管IPD流程图可能初看之下显得错综复杂,但只需掌握几个关键点,你便能轻松驾驭...
IPD开发流程管理   8  
  在项目管理领域,集成产品开发(IPD)流程被视为提升产品上市速度、增强团队协作与创新能力的重要工具。然而,尽管IPD流程拥有诸多优势,其实施过程中仍可能遭遇多种挑战,导致项目失败。本文旨在深入探讨八个常见的IPD流程失败原因,并提出相应的解决方法,以帮助项目管理者规避风险,确保项目成功。缺乏明确的项目目标与战略对齐IP...
IPD流程图   8  
热门文章
项目管理软件有哪些?
云禅道AD
禅道项目管理软件

云端的项目管理软件

尊享禅道项目软件收费版功能

无需维护,随时随地协同办公

内置subversion和git源码管理

每天备份,随时转为私有部署

免费试用