我如何融化Pandas Dataframe ?

iecba09b  于 2022-12-21  发布在  其他
关注(0)|答案(3)|浏览(140)

pandas标签上,我经常看到用户问关于在Pandas中融化 Dataframe 的问题,我将尝试一个关于这个主题的经典问答(自我回答)。
我要澄清一下:
1.什么是融化?
1.如何使用融化?
1.何时使用融化?
我看到一些关于融化的热门问题,例如:

因此,我将尝试一个规范的问答这个主题。

数据集:

我将把我所有的答案都放在这个随机年龄的随机人的随机等级数据集上(答案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:

如何融化 Dataframe ,使原始 Dataframe 变为:

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

我希望输出像上面一样。

问题三:

如果我是分组融化和排序的学生有分数,我将如何能够做到这一点,以获得所需的输出像下面:

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:

我如何 unmelt 一个融化的 Dataframe ?假设我已经融化了这个 Dataframe :

print(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

那么,我如何将其转换回原始 Dataframe ,如下所示:

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

我该怎么做呢?

问题五:

如果我按学生的名字分组,用逗号分隔科目和成绩,我会怎么做?

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

我想有一个像上面的 Dataframe 。

问题6:

如果我要完全融化我的 Dataframe ,所有列都是值,我该怎么做?

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

我想有一个像上面的数据框。所有列的值。

请检查我下面的自我回答:)

nkoocmlb

nkoocmlb1#

    • panda版本〈0.20.0的注意事项**:我将在示例中使用df.melt(...),但您需要使用pd.melt(df, ...)

文件参考:

这里的大多数解都将与melt一起使用,因此要了解melt方法,请参见documentaion说明
将DataFrame从宽格式取消透视为长格式,可以选择保留设置的标识符。
此函数可用于将DataFrame转换为一种格式,其中一个或多个列是标识符变量 (id_vars),而所有其他列(被视为测量变量 (value_vars))"取消透视"到行轴,只留下两个非标识符列,"variable"和"value"。

    • 参数 *
      • 标识变量**:* 元组、列表或ndarray,可选 *

要用作标识符变量的列。

      • 值变量**:* 元组、列表或ndarray,可选 *

要取消透视的列。如果未指定,则使用未设置为id_vars的所有列。

      • 变量名称**:* 标量 *

Name to use for the ‘variable’ column. If None it uses frame.columns.name or ‘variable’.

      • 值名称**:* 标量,默认"值"*

用于"值"列的名称。

      • 列级别**:* 整型或字符串,可选 *

如果列是MultiIndex,则使用此级别进行融合。

      • 忽略索引**:* 布尔值,默认为True *

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

  • 1.1.0版中的新增功能。*

熔化逻辑:

熔解合并多个列并将 Dataframe 从宽转换为长,对于问题1的解决方案(见下文),步骤是:
1.首先我们得到了原始 Dataframe 。
1.然后,熔解首先合并MathEnglish列,并复制 Dataframe (更长)。
1.最后添加列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)
)

这段代码将NameAge列设置为索引,堆叠其余的MathEnglish列,重置索引并将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可以用meltgroupby解决,使用agg函数和', '.join,如下所示:

print(
    df.melt(id_vars=["Name", "Age"])
    .groupby("value", as_index=False)
    .agg(", ".join)
)

它先分解 Dataframe ,然后按等级分组,然后聚合它们,最后用逗号连接它们。
stack也可用于解决此问题,stackgroupby如下所示:

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等效的方式转置 Dataframe ,然后重置索引,重命名列、组和聚合。
两种解决方案输出:

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:

我们首先融化输入数据的 Dataframe :

df = df.melt(id_vars=['Name', 'Age'], var_name='Subject', value_name='Grades')

那么现在我们可以开始解决问题4了。
问题4可以用pivot_table来解决,我们必须指定pivot_table参数,valuesindexcolumnsaggfunc
我们可以用下面的代码来解决这个问题:

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

融化的 Dataframe 被转换回与原始 Dataframe 完全相同的格式。
我们首先透视融化的 Dataframe ,然后重置索引并删除列轴名称。

问题五:

问题5可以使用meltgroupby解决,如下所示:

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'))

这会融化整个 Dataframe
或者您可以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

结论:

melt是一个非常方便的函数,通常是必需的,一旦你遇到这类问题,不要忘记尝试melt,它可能会很好地解决你的问题。

oymdgrw7

oymdgrw72#

还有一种melt在问题中没有提到,那就是对于列标题包含公共前缀的 Dataframe ,您希望将后缀融化到列值中。
这与How can I pivot a dataframe?中的 * 问题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
x一个一个一个一个x一个一个二个x

csga3l58

csga3l583#

如U12-Forward中的here所述,melt处理 Dataframe 主要意味着将数据从宽格式重新整形为长格式。通常情况下,与原始 Dataframe 相比,新 Dataframe 将具有更多的行和更少的列。
当涉及到融合时有不同的场景-所有列标签可以被融合到单个列或多个列中;列标签的某些部分可以保留为标题,而其余部分则整理成一列,以此类推。这个答案展示了如何使用pd.stackpd.meltpd.wide_to_long和pivot_longger from pyjanitor来融化panda Dataframe (我是pyjanitor库的贡献者)。这些例子并不详尽,但希望能为您指出正确的方向,当谈到重塑 Dataframe 从宽到长的形式。

示例数据

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.meltpd.stack来完成,问题1的solutions已经涵盖了这一点。

# 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中一样,您可以通过向names_tovalues_to参数传递参数来重命名variablevalue列:

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

您还可以保留原始索引,并根据出现顺序保留 Dataframe :

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参数转换为其他数据类型--这对于大型 Dataframe 可能很有帮助/很有性能,因为与整形后转换数据类型相比,它通常更高效。请注意,此功能目前仅在开发版本中可用:

# pip install git+https://github.com/pyjanitor-devs/pyjanitor.git

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列下。而lengthwidthdimension列中:

  • 通过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
  • 通过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_longger是如何将列标签拆分成多个新列的,只要有一个定义好的分隔符。如果没有一个明确定义的分隔符,比如下面的数据框:

# 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

在第二列中,我们有多个_,而第三列只有一个_。(spreldiagnosis列,mfgender列,数字到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 -将列标签 * 和 * 值合并到多列中:

如果我们也想将值拆分到多个列中呢?让我们使用一个简单的popular question on 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整理到结果列中,将GinVodka整理到Drinks列中,并将各自的值分别整理到PoundsOunces中。

  • 通过pd.melt-我正在逐字复制优秀的solution
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

随着 Dataframe 大小的增加,效率甚至更高。

方案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 -这里我们拆分列并构建一个MultiIndex:
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
  • Via 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
  • Via pivot_longger--同样,对于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 -保留部分列名作为标题:

这可能是将列标签重新整形为长格式时最大的用例之一。我们可能希望保留列标签的某些部分作为标题,并将其余列移动到新列(甚至忽略它们)。
让我们回顾一下虹膜 Dataframe :

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

这不如下面的其他选项有效,因为这涉及宽到长,* 然后 * 长到宽,这可能在足够大的 Dataframe 上性能较差。

  • 通过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的效率可能会降低。

  • 通过枢轴_更长:再次回到模式。因为我们保留了列的一部分作为标题,所以我们使用.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相关联,而Lengthdimension相关联。Petal仍然作为列标题。而Length被集中到dimension列,我们不需要明确列名称,我们只使用.value,让函数来完成繁重的工作,这样,如果你有很多列,只要通过names_sepnames_pattern获得了正确的模式,就不需要计算作为标题的列应该是什么。
如果我们想用Length/Width作为列名,而Petal/Sepal被集中到part列中,该怎么办?

  • 通过PD熔融
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堆栈:
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
  • Via 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
  • 通过枢轴_更长:
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拆分得到的任何 Dataframe 配对,并输出整形后的 Dataframe 。在适用的情况下,您甚至可以使用多个.value。让我们回顾一下前面的 Dataframe :
一个三个一个一个一个二个一个
这一切都是关于看到模式并利用它们。pivot_longer只是在常见的整形场景中提供了高效和性能的抽象-在引擎盖下,它只是Pandas/numpy/python。
希望当你需要从宽到长重塑时,各种答案能为你指明正确的方向。

相关问题