将 pandas 数据框从行重塑为列

Reshape pandas dataframe from rows to columns

我正在尝试重塑我的数据。乍一看,这听起来像是转置,但实际上不是。我试过融化、stack/unstack、连接等

用例

我希望每个唯一的个人只有一行,并将所有工作历史记录放在列中。对于客户来说,跨行阅读信息比逐列阅读更容易。

这是数据:

import pandas as pd
import numpy as np

data1 = {'Name': ["Joe", "Joe", "Joe","Jane","Jane"],
        'Job': ["Analyst","Manager","Director","Analyst","Manager"],
        'Job Eff Date': ["1/1/2015","1/1/2016","7/1/2016","1/1/2015","1/1/2016"]}
df2 = pd.DataFrame(data1, columns=['Name', 'Job', 'Job Eff Date'])

df2

这是我想要的样子: Desired Output Table

这与您的要求不完全相同,但这是一种根据需要打印数据框的方法:

df = pd.DataFrame(data1)
for name, jobs in df.groupby('Name').groups.iteritems():
    print '{0:<15}'.format(name),
    for job in jobs:
        print '{0:<15}{1:<15}'.format(df['Job'].ix[job], df['Job Eff Date'].ix[job]),
    print

## Jane            Analyst        1/1/2015        Manager        1/1/2016       
## Joe             Analyst        1/1/2015        Manager        1/1/2016        Director       7/1/2016    

假设您从取消堆叠开始:

df2 = df2.set_index(['Name', 'Job']).unstack()
>>> df2
    Job Eff Date
Job Analyst Director    Manager
Name            
Jane    1/1/2015    None    1/1/2016
Joe 1/1/2015    7/1/2016    1/1/2016
In [29]:

df2

现在,为了让事情变得更简单,展平多重索引:

df2.columns = df2.columns.get_level_values(1)
>>> df2
Job Analyst Director    Manager
Name            
Jane    1/1/2015    None    1/1/2016
Joe 1/1/2015    7/1/2016    1/1/2016

现在,只需操作列:

cols = []
for i, c in enumerate(df2.columns):
    col = 'Job %d' % i
    df2[col] = c
    cols.append(col)
    col = 'Eff Date %d' % i
    df2[col] = df2[c]
    cols.append(col)
>>> df2[cols]
Job Job 0   Eff Date 0  Job 1   Eff Date 1  Job 2   Eff Date 2
Name                        
Jane    Analyst 1/1/2015    Director    None    Manager 1/1/2016
Joe Analyst 1/1/2015    Director    7/1/2016    Manager 1/1/2016

编辑

简从来都不是导演(唉)。上面的代码表明 Jane 在 None 日期成为董事。要更改结果以指定 Jane 在 None 日期成为 None(这是个人喜好问题),请替换

df2[col] = c

来自

df2[col] = [None if d is None else c for d in df2[c]]

这给

Job Job 0   Eff Date 0  Job 1   Eff Date 1  Job 2   Eff Date 2
Name                        
Jane    Analyst 1/1/2015    None    None    Manager 1/1/2016
Joe Analyst 1/1/2015    Director    7/1/2016    Manager 1/1/2016

这是一个可能的解决方法。在这里,我首先创建一个适当形式的字典,并根据新字典创建一个 DataFrame:

df = pd.DataFrame(data1)

dic = {}

for name, jobs in df.groupby('Name').groups.iteritems():
    if not dic:
        dic['Name'] = []
    dic['Name'].append(name)
    for j, job in enumerate(jobs, 1):
        jobstr = 'Job {0}'.format(j)
        jobeffdatestr = 'Job Eff Date {0}'.format(j)
        if jobstr not in dic:
            dic[jobstr] = ['']*(len(dic['Name'])-1)
            dic[jobeffdatestr] = ['']*(len(dic['Name'])-1)
        dic[jobstr].append(df['Job'].ix[job])
        dic[jobeffdatestr].append(df['Job Eff Date'].ix[job])

df2 = pd.DataFrame(dic).set_index('Name')

##         Job 1    Job 2     Job 3 Job Eff Date 1 Job Eff Date 2 Job Eff Date 3
## Name                                                                         
## Jane  Analyst  Manager                 1/1/2015       1/1/2016               
## Joe   Analyst  Manager  Director       1/1/2015       1/1/2016       7/1/2016
g = df2.groupby('Name').groups
names = list(g.keys())
data2 = {'Name': names}
cols = ['Name']
temp1 = [g[y] for y in names]
job_str = 'Job'
job_date_str = 'Job Eff Date'
for i in range(max([len(x) for x in g.values()])):
    temp = [x[i] if len(x) > i else '' for x in temp1]
    job_str_curr = job_str + str(i+1)
    job_date_curr = job_date_str + str(i + 1)
    data2[job_str + str(i+1)] = df2[job_str].ix[temp].values
    data2[job_date_str + str(i+1)] = df2[job_date_str].ix[temp].values
    cols.extend([job_str_curr, job_date_curr])

df3 = pd.DataFrame(data2, columns=cols)
df3 = df3.fillna('')
print(df3)
   Name     Job1 Job Eff Date1     Job2 Job Eff Date2      Job3 Job Eff Date3
0  Jane  Analyst      1/1/2015  Manager      1/1/2016                        
1   Joe  Analyst      1/1/2015  Manager      1/1/2016  Director      7/1/2016

.T groupby

以内
def tgrp(df):
    df = df.drop('Name', axis=1)
    return df.reset_index(drop=True).T

df2.groupby('Name').apply(tgrp).unstack()


说明

groupby returns 包含有关原始系列或数据框如何分组的信息的对象。我们可以先将 df2.groupby('Name') 分配给一个变量(我经常这样做),比如 gb.

,而不是执行带有某种后续操作的 groupby
gb = df2.groupby('Name')

在这个对象上 gb 我们可以调用 .mean() 来获得每组的平均值。或者 .last() 获取每组的最后一个元素(行)。或者 .transform(lambda x: (x - x.mean()) / x.std()) 以获得每个组内的 zscore 转换。当你想在一个没有预定义功能的组中做某事时,仍然有 .apply().

.apply() 对于 groupby 对象不同于对于 dataframe。对于数据框,.apply() 将可调用对象作为其参数并将该可调用对象应用于对象中的每一列(或行)。传递给该可调用对象的对象是 pd.Series。当您在 dataframe 上下文中使用 .apply 时,牢记这一点会很有帮助。在 groupby 对象的上下文中,传递给可调用参数的对象是数据帧。事实上,该数据框是 groupby.

指定的组之一

当我编写这样的函数传递给 groupby.apply 时,我通常将参数定义为 df 以反映它是一个数据帧。

好的,所以我们有:

df2.groupby('Name').apply(tgrp)

这会为每个 'Name' 生成一个子数据帧,并将该子数据帧传递给函数 tgrp。然后 groupby 对象将所有经过 tgrp 函数的这些组重新组合在一起。

看起来像这样。

我把 OP 最初的尝试简单地转移到了心里。但我必须先做一些事情。如果我只是做了:

df2[df2.Name == 'Jane'].T

df2[df2.Name == 'Joe'].T

手动组合这些(没有 groupby):

pd.concat([df2[df2.Name == 'Jane'].T, df2[df2.Name == 'Joe'].T])

哇!现在这很丑陋。显然 [0, 1, 2] 的索引值与 [3, 4] 不相符。所以让我们重新设置。

pd.concat([df2[df2.Name == 'Jane'].reset_index(drop=True).T,
           df2[df2.Name == 'Joe'].reset_index(drop=True).T])

好多了。但现在我们正在进入 groupby 原本打算处理的领域。所以让它处理它。

返回

df2.groupby('Name').apply(tgrp)

这里唯一缺少的是我们想要拆开结果以获得所需的输出。

深入了解@piRSquared 的答案....

def tgrp(df):
    df  = df.drop('Name', axis=1)
    print df, '\n'   
    out =  df.reset_index(drop=True)   
    print out, '\n'
    out.T 
    print out.T, '\n\n'
    return  out.T

dfxx = df2.groupby('Name').apply(tgrp).unstack()
dfxx

上面的输出。为什么 pandas 重复第一组?这是一个错误吗?

       Job Job Eff Date
3  Analyst     1/1/2015
4  Manager     1/1/2016 

       Job Job Eff Date
0  Analyst     1/1/2015
1  Manager     1/1/2016 

                     0         1
Job            Analyst   Manager
Job Eff Date  1/1/2015  1/1/2016 


       Job Job Eff Date
3  Analyst     1/1/2015
4  Manager     1/1/2016 

       Job Job Eff Date
0  Analyst     1/1/2015
1  Manager     1/1/2016 

                     0         1
Job            Analyst   Manager
Job Eff Date  1/1/2015  1/1/2016 


        Job Job Eff Date
0   Analyst     1/1/2015
1   Manager     1/1/2016
2  Director     7/1/2016 

        Job Job Eff Date
0   Analyst     1/1/2015
1   Manager     1/1/2016
2  Director     7/1/2016 

                     0         1         2
Job            Analyst   Manager  Director
Job Eff Date  1/1/2015  1/1/2016  7/1/2016