将pandas数据框从行转为列

10

我正在尝试重塑我的数据。乍一看,它听起来像是一个转置,但实际上不是。我尝试了melts、stack/unstack、joins等方法。

用例

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

以下是数据:

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

这是我想要的样子: 期望输出表格 输入图片说明
6个回答

9

groupby 中的 .T

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

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

enter image description here


解释

groupby 返回一个包含原始系列或数据帧已被分组信息的对象。我们可以先将 df2.groupby('Name') 分配给一个变量(我经常这样做),比如说 gb,而不是执行一个后续的操作。

gb = df2.groupby('Name')

在这个对象gb上,我们可以调用.mean()来获取每个组的平均值。或者使用.last()来获取每个组的最后一个元素(行)。或者使用.transform(lambda x: (x - x.mean()) / x.std())在每个组内获取zscore转换。当您想要在组内执行没有预定义函数的操作时,仍然可以使用.apply()
对于groupby对象的.apply()dataframe的不同。对于数据框,.apply()以可调用对象作为其参数,并将该可调用对象应用于对象中的每个列(或行)。传递给该可调用对象的对象是pd.Series。当您在dataframe上下文中使用.apply时,牢记这一点非常有帮助。在groupby对象的上下文中,传递给可调用参数的对象是数据框。实际上,该数据框是由groupby指定的其中一个组。
当我编写这样的函数来传递给groupby.apply时,通常将参数定义为df以反映它是一个数据帧。
好的,所以我们有:
df2.groupby('Name').apply(tgrp)

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

效果如下。

enter image description here

我将原作者的简单转置尝试认真对待了,但首先我需要做一些事情。如果我只是这样做了:
df2[df2.Name == 'Jane'].T

enter image description here

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

enter image description here

手动合并这些(不使用groupby):

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

enter image description here

哇!这太丑了。显然,[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])

enter image description here

这样就好多了。但现在我们进入了 groupby 应该处理的领域,所以让它来处理吧。

回到

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

这里唯一缺失的是我们需要取消堆叠结果以获得所需的输出。

enter image description here


我该如何检查类似这样的东西以深入挖掘并找出类似这样的东西... 对于我来说,groupby对象仍然是有时成功有时失败。 - Merlin
我已将其分解.. 我有一个问题要问你,它被列为答案--但它并不是一个答案。 - Merlin
我不明白df是如何被传递回来的?它实际上并不存在于数据集中(原始数据集相关)。 - June
@Christopher 我更新了帖子并加入了一些解释。 - piRSquared
感谢详细的写作。我仍然不清楚 tgrp 是如何工作的? - June
取出 df2[df2.Name == 'Jane'] 并在其上运行 tgrp。像这样:tgrp(df2[df2.Name == 'Jane'])。观察 tgrp 前后的 df2[df2.Name == 'Jane'],看看是否有帮助。 - piRSquared

2

假设你开始的时候是要解除堆叠:

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从未担任过董事(唉)。上述代码说明Jane在日期成为了董事。要更改结果,使其指定Jane在日期成为,请进行以下替换(这是一种口味问题):

df2[col] = c

by

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


谢谢Ami。我觉得这段代码给Jane多了一个任务? - June
1
@Christopher 她就不能抱怨了,对吧? :-) 我会调查一下。 - Ami Tavory
@Christopher 我认为这更多是品味问题,但请看更新。 - Ami Tavory

1
这里有一个可能的解决方法。我首先创建了一个正确格式的字典,并基于新字典创建了一个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

谢谢Julien。我收到了以下错误:

AttributeError Traceback (most recent call last) <ipython-input-2-052b194814e6> in <module>() 3 dic = {} 4 ----> 5 for name, jobs in df.groupby('Name').groups.iteritems(): 6 if not dic: 7 dic['Name'] = []错误原因:'dict'对象没有'iteritems'属性。
- June
不客气 :-) 如果您使用的是Python 3,只需删除“.iteritems()”即可。 - Julien Spronck

1
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

谢谢Ophir。我不知道从哪里开始理解这个是如何工作的。有没有什么资源可以让我了解更多? - June
gropby : http://pandas.pydata.org/pandas-docs/stable/generated/pandas.DataFrame.groupby.html - Ophir Carmi
fillna: http://pandas.pydata.org/pandas-docs/stable/generated/pandas.DataFrame.fillna.html - Ophir Carmi
ix: http://pandas.pydata.org/pandas-docs/stable/generated/pandas.DataFrame.ix.html - Ophir Carmi

0

这不完全是你所问的,但以下是按照你要求打印数据框的方法:

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    

谢谢 Julien。我遇到了一个错误:文件“<ipython-input-3-216784a9e7ca>”,第3行 print '{0:<15}'.format(name), ^ SyntaxError: invalid syntax我需要在数据框中做更多的工作,所以不确定这个解决方案是否可行。不过这是一个有趣的想法。 - June
不用谢。我猜你正在使用Python 3。请将所有的print blahblah替换为print(blahblah) - Julien Spronck

0
深入研究 @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会重复第一组?这是一个bug吗?
       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 

网页内容由stack overflow 提供, 点击上面的
可以查看英文原文,
原文链接