楔子

笔者曾经碰到过两种格式的数据,当时确实把我难住了,最后虽然解决了,但是方法不够优雅,而且效率也不高,如果想高效率,那么就必须使用pandas提供的方法。而pandas作为很强的一个库,一定可以优雅地解决。当时用自己的方法解决之后,就没有之后了。但是最近又碰到了当时的情况,于是决定要优雅地解决,最后经过努力总算找到了解决的办法,下面先来看看当时难住笔者的两种格式的数据、以及需求吧。

需求一:

有以下格式的数据:

姓名 科目 成绩
小红 语文 90
小红 数学 90
小红 英语 90
小胖 语文 91
小胖 数学 91
小胖 英语 91
小花 语文 92
小花 数学 92
小花 英语 92

我们要变成以下的样子

姓名 语文 数学 英语
小红 90 90 90
小胖 91 91 91
小花 92 92 92

需求二:

姓名 年龄 爱好
小红 18 跳舞,唱歌,钢琴
小胖 20 唱,跳,rap,篮球
小花 19 古筝,翻译

我们要变成以下的样子

姓名 年龄 爱好
小红 18 跳舞
小红 18 唱歌
小红 18 钢琴
小胖 20 唱
小胖 20 跳
小胖 20 rap
小胖 20 篮球
小花 19 古筝
小花 19 翻译

解决需求一

unstack

print(df)
"""
姓名 科目 分数
0 小红 语文 90
1 小红 数学 90
2 小红 英语 90
3 小胖 语文 91
4 小胖 数学 91
5 小胖 英语 91
6 小花 语文 92
7 小花 数学 92
8 小花 英语 92
"""
# 将姓名和科目设置索引,然后只取出"分数",得到对应的二级索引Series对象
df = df.set_index(["姓名", "科目"])
two_level_index_series = df["分数"]
# 此时得到的是一个具有二级索引的series
print(two_level_index_series)
"""
姓名 科目
小红 语文 90
数学 90
英语 90
小胖 语文 91
数学 91
英语 91
小花 语文 92
数学 92
英语 92
Name: 分数, dtype: int64
"""
# 调用二级索引的unstack方法,会得到一个DataFrame
# 并且会自动把一级索引变成DataFrame的索引,二级索引变成DataFrame的列
new_df = two_level_index_series.unstack()
print(new_df)
"""
科目 数学 英语 语文
姓名
小红 90 90 90
小胖 91 91 91
小花 92 92 92
"""
# 怎么样是不是改回来了呢?但是还有不完美的地方
# 那就是这个new_df的index和columns都有名字
# index的名字就是"姓名",columns的名字就是"科目",因为原来的series的两个索引就叫"姓名"和"分数"
# rename_axis表示给坐标轴重命名
# 这里先把columns的名字变为空,至于index不为空的原因继续看
new_df = new_df.rename_axis(columns=None)
print(new_df)
"""
数学 英语 语文
姓名
小红 90 90 90
小胖 91 91 91
小花 92 92 92
"""
new_df = new_df.reset_index()
print(new_df)
"""
姓名 数学 英语 语文
0 小红 90 90 90
1 小胖 91 91 91
2 小花 92 92 92
"""
# 大功告成,如果index变为空的话,那么在reset_index之后,列名会变成index
# 但是如果原来索引有名字,reset_index,列名就是原来的索引名

pivot

pivot相当于是我们上面方法的一个化简,我们是把姓名作为索引、科目作为列、分数作为值

print(df)
"""
姓名 科目 分数
0 小红 语文 90
1 小红 数学 90
2 小红 英语 90
3 小胖 语文 91
4 小胖 数学 91
5 小胖 英语 91
6 小花 语文 92
7 小花 数学 92
8 小花 英语 92
""" df = pd.pivot(df, index="姓名", columns="科目", values="分数")
print(df)
"""
科目 数学 英语 语文
姓名
小红 90 90 90
小胖 91 91 91
小花 92 92 92
"""
# 可以看到上面这一步,就直接相当于df.set_index(["姓名", "科目"])["分数"].unstack() df = df.rename_axis(columns=None).reset_index()
print(df)
"""
姓名 数学 英语 语文
0 小红 90 90 90
1 小胖 91 91 91
2 小花 92 92 92
"""

解决需求二:

print(df)
"""
姓名 年龄 爱好
小红 18 跳舞,唱歌,钢琴
小胖 20 唱,跳,rap,篮球
小花 19 古筝,翻译
""" df = df.set_index(["姓名", "年龄"])["爱好"].str.split(",", expand=True).stack().reset_index(drop=True, level=-1).reset_index().rename(columns={0: "爱好"})
print(df)
"""
姓名 年龄 爱好
0 小红 18 跳舞
1 小红 18 唱歌
2 小红 18 钢琴
3 小胖 20 唱
4 小胖 20 跳
5 小胖 20 rap
6 小胖 20 篮球
7 小花 19 古筝
8 小花 19 翻译
"""

估计有人会懵逼,别急我们来一步一步拆解,不过在此之前我们先来介绍一下unstack和stack

unstack和stack

首先Series只有unstack,DataFrame既有unstack又有stack。对于Series来说,我们刚才说了,unstack是把该Series变成一个DataFrame,并且会把当前的一级索引变成DataFrame的对应索引、二级索引变成DataFrame的对应列,但如果不止二级呢?假设这个Series有8级索引呢?其实不管有多少级,假设n级,unstack不加参数的话,那么默认是把最后一级索引变成DataFrame的列,前面的n-1个索引则依旧会变成DataFrame的索引,当然也是n-1个。

为了和DataFrame做对比,我们就假设为2级索引。对于Series来说,unstack是把1级索引变成对应DataFrame的索引,2级索引是变成对应DataFrame的列。如果对DataFrame调用unstack,那么会把这个DataFrame转成一个具有二级索引的Series(如果这个DataFrame的索引只有一级的话),对应的索引变成具有二级索引的Series的二级索引,对应的列变成具有二级索引Series的一级索引。如果是stack的话,那么和Series正好是相反的,DataFrame的索引变成具有二级索引Series的一级索引,列变成具有二级索引Series的二级索引。

文字不好懂的话,看一张图

下面我们就来分析一下上面的那一长串

print(df)
"""
姓名 年龄 爱好
小红 18 跳舞,唱歌,钢琴
小胖 20 唱,跳,rap,篮球
小花 19 古筝,翻译
""" # 我们是对"爱好"这个字段进行分解
# 那么将除了"爱好"之外的其它字段设置为索引
df = df.set_index(["姓名", "年龄"])
print(df)
"""
爱好
姓名 年龄
小红 18 跳舞,唱歌,钢琴
小胖 20 唱,跳,rap,篮球
小花 19 古筝,翻译
""" # 筛选出"爱好"这个字段,此时得到的是一个具有二级索引的Series
# 索引的名字叫 "姓名"和"年龄"
s = df["爱好"]
print(s)
"""
姓名 年龄
小红 18 跳舞,唱歌,钢琴
小胖 20 唱,跳,rap,篮球
小花 19 古筝,翻译
Name: 爱好, dtype: object
""" # 那么下面就对期望的字段进行分解
# 我们这个例子都是以逗号为分隔符,至于具体是什么则以实际数据为准
# 显然这里得到一个具有二级索引的DataFrame
df = s.str.split(",", expand=True)
print(df)
"""
0 1 2 3
姓名 年龄
小红 18 跳舞 唱歌 钢琴 None
小胖 20 唱 跳 rap 篮球
小花 19 古筝 翻译 None None
""" # 调用stack,按照前面说的,会变成一个Series,索引就是DataFrame的索引再加上这个列变成的索引,显然加上列变成的索引就是三级索引了
# 不过可以把DataFrame的索引看成一个整体作为对应Series的一级索引,然后列变成的索引对应二级索引
s = df.stack()
# 此时的数据已经像那么回事了
print(s)
"""
姓名 年龄
小红 18 0 跳舞
1 唱歌
2 钢琴
小胖 20 0 唱
1 跳
2 rap
3 篮球
小花 19 0 古筝
1 翻译
dtype: object
""" # 然后调用reset_index,但是我们发现索引有三级,那么这样做就会导致,0 1 2 0 1 2..这些也变成了一列,当然可以之后drop掉
# 但是我们也可以直接删掉
# 于是我们可以加上一个drop=True,但是这样又把所有的index都删掉了,于是我们可以指定一个level
# 由于三级索引,那么最后一级就是2,当然可以直接指定为-1,表示最后一级,表示把最后一级索引删掉
s = s.reset_index(drop=True, level=-1)
print(s)
"""
姓名 年龄
小红 18 跳舞
18 唱歌
18 钢琴
小胖 20 唱
20 跳
20 rap
20 篮球
小花 19 古筝
19 翻译
dtype: object
""" # 但是我们发现,上面的reset_index(drop=True, level=-1)并没有把前面的索引变成列
# 这是因为我们指定了level,如果不指定level,那么drop=True会把所有的索引都删掉
# 但指定了level只会删除对应级别的索引,而不会同时对前面的索引进行reset,于是需要再调用一次reset_index,此时就什么也不需要指定了
df = s.reset_index()
# 会自动进行笛卡尔乘积
print(df)
"""
姓名 年龄 0
0 小红 18 跳舞
1 小红 18 唱歌
2 小红 18 钢琴
3 小胖 20 唱
4 小胖 20 跳
5 小胖 20 rap
6 小胖 20 篮球
7 小花 19 古筝
8 小花 19 翻译
""" # 但是我们发现列名,是自动生成的0,于是再进行rename
df = df.rename(columns={0: "爱好"})
print(df)
"""
姓名 年龄 爱好
0 小红 18 跳舞
1 小红 18 唱歌
2 小红 18 钢琴
3 小胖 20 唱
4 小胖 20 跳
5 小胖 20 rap
6 小胖 20 篮球
7 小花 19 古筝
8 小花 19 翻译
"""
# 此时就大功告成啦

但是今天发现了一个更简单的方法,那就hive里面explode在pandas里面也是支持的

print(df)
"""
姓名 年龄 爱好
小红 18 跳舞,唱歌,钢琴
小胖 20 唱,跳,rap,篮球
小花 19 古筝,翻译
"""
df["爱好"] = df["爱好"].str.split(",")
print(df)
"""
姓名 年龄 爱好
0 小红 18 [跳舞, 唱歌, 钢琴]
1 小胖 20 [唱, 跳, rap, 篮球]
2 小花 19 [古筝, 翻译]
"""
# "爱好"这个字段变成了列表,我们可以直接对其进行"炸裂"
print(df.explode("爱好"))
"""
姓名 年龄 爱好
0 小红 18 跳舞
0 小红 18 唱歌
0 小红 18 钢琴
1 小胖 20 唱
1 小胖 20 跳
1 小胖 20 rap
1 小胖 20 篮球
2 小花 19 古筝
2 小花 19 翻译
"""

新需求

但是笔者最近又遇到了两个需求,第一个需求,我们需要对数据进行如下的变换:

    id                                       info
0 001 {'name': 'mashiro', 'age': 17, 'gender': 'female'}
1 002 {'name': 'satori', 'age': 16, 'gender': 'female'}
2 003 {'name': 'nagisa', 'age': 21, 'gender': 'female', 'where': 'clannad'} 格式如上面所示,我需要变成下面的格式
id name age gender where
0 001 mashiro 17 female NaN
1 002 satori 16 female NaN
2 003 nagisa 21 female clannad

在pandas中有一个快速的办法

print(df)
"""
id info
0 001 {'name': 'mashiro', 'age': 17, 'gender': 'female'}
1 002 {'name': 'satori', 'age': 16, 'gender': 'female'}
2 003 {'name': 'nagisa', 'age': 21, 'gender': 'female', 'where': 'clannad'}
"""
print(df["info"].apply(pd.Series))
"""
name age gender where
0 mashiro 17 female NaN
1 satori 16 female NaN
2 nagisa 21 female clannad
"""

我们看到,只需要对info这一列使用apply(pd.Series),即可将字典变成多个字段,列名就是字典的key,如果不存在就用NaN填充。

当然笔者遇到的没有这么简单,"info"字段的值不再是字典,而是一个列表,列表里面是多个字典。

但是也不难,我们可以使用explode函数对"info"字段进行炸裂,生成多行,这样info的每一行就是字典了,然后再apply(pd.Series)即可

所以上面的就不难了

print(df)
"""
id info
0 001 {'name': 'mashiro', 'age': 17, 'gender': 'female'}
1 002 {'name': 'satori', 'age': 16, 'gender': 'female'}
2 003 {'name': 'nagisa', 'age': 21, 'gender': 'female', 'where': 'clannad'}
"""
tmp = df["info"].apply(pd.Series)
# 将tmp添加到df中
df[tmp.columns] = tmp
# 删掉原来的info字段
df = df.drop(columns=["info"])
# 大功告成
print(df)
"""
id name age gender where
0 001 mashiro 17 female NaN
1 002 satori 16 female NaN
2 003 nagisa 21 female clannad
"""

第二个需求如下:

   水果 销售员  星期一  星期二  星期三
0 草莓 张三 30斤 38斤 35斤
1 芒果 李四 20斤 10斤 18斤
2 桃子 王五 35斤 25斤 29斤 格式如上面所示,我需要变成下面的格式
水果 销售员 日期 销售量
0 草莓 张三 星期一 30斤
1 芒果 李四 星期一 20斤
2 桃子 王五 星期一 35斤
3 草莓 张三 星期二 38斤
4 芒果 李四 星期二 10斤
5 桃子 王五 星期二 25斤
6 草莓 张三 星期三 35斤
7 芒果 李四 星期三 18斤
8 桃子 王五 星期三 29斤

事实上这个需求可以通过上面介绍的stack实现,但是需要将"水果"和"销售员"设置为索引,然后再stack、最后reset_index。而在pandas中,实际上还有一个更加方便的方法:pd.melt

print(df)
"""
水果 销售员 星期一 星期二 星期三
0 草莓 张三 30斤 38斤 35斤
1 芒果 李四 20斤 10斤 18斤
2 桃子 王五 35斤 25斤 29斤
"""
# 我们目的是根据"星期一"、"星期二"、"星期三"这三列生成多行,所以:
print(pd.melt(df, id_vars=["水果", "销售员"], value_vars=["星期一", "星期二", "星期三"]))
"""
水果 销售员 variable value
0 草莓 张三 星期一 30斤
1 芒果 李四 星期一 20斤
2 桃子 王五 星期一 35斤
3 草莓 张三 星期二 38斤
4 芒果 李四 星期二 10斤
5 桃子 王五 星期二 25斤
6 草莓 张三 星期三 35斤
7 芒果 李四 星期三 18斤
8 桃子 王五 星期三 29斤
"""
# 但是生成多行之后,列名自动变成了variable和value
# 我们可以手动rename,也可以在melt函数里面指定
print(pd.melt(df,
id_vars=["水果", "销售员"],
value_vars=["星期一", "星期二", "星期三"],
var_name="日期",
value_name="销售量"))
"""
水果 销售员 日期 销售量
0 草莓 张三 星期一 30斤
1 芒果 李四 星期一 20斤
2 桃子 王五 星期一 35斤
3 草莓 张三 星期二 38斤
4 芒果 李四 星期二 10斤
5 桃子 王五 星期二 25斤
6 草莓 张三 星期三 35斤
7 芒果 李四 星期三 18斤
8 桃子 王五 星期三 29斤
""" # 也可以只指定部分字段
print(pd.melt(df,
id_vars=["水果"],
value_vars=["星期一", "星期二"],
var_name="日期",
value_name="销售量"))
"""
水果 日期 销售量
0 草莓 星期一 30斤
1 芒果 星期一 20斤
2 桃子 星期一 35斤
3 草莓 星期二 38斤
4 芒果 星期二 10斤
5 桃子 星期二 25斤
""" # 如果只指定id_vars,那么默认将剩余的所有字段作为value_vars
print(pd.melt(df,
id_vars=["水果"],
var_name="日期",
value_name="销售量"))
# 我们看到它将"销售员"这个字段也算进去了
"""
水果 日期 销售量
0 草莓 销售员 张三
1 芒果 销售员 李四
2 桃子 销售员 王五
3 草莓 星期一 30斤
4 芒果 星期一 20斤
5 桃子 星期一 35斤
6 草莓 星期二 38斤
7 芒果 星期二 10斤
8 桃子 星期二 25斤
9 草莓 星期三 35斤
10 芒果 星期三 18斤
11 桃子 星期三 29斤
"""

所以这里的melt函数和上面的explode函数算是stack和unstack的一个很好的替代品,它们可以很方便的就达到我们想要的效果了。

但是我们说这个需求和最上面的需求二比较类似,我们完全可以使用stack函数来解决,来看一下

print(df)
"""
水果 销售员 星期一 星期二 星期三
0 草莓 张三 30斤 38斤 35斤
1 芒果 李四 20斤 10斤 18斤
2 桃子 王五 35斤 25斤 29斤
"""
print(df.set_index(["水果", "销售员"]).stack().reset_index())
"""
水果 销售员 level_2 0
0 草莓 张三 星期一 30斤
1 草莓 张三 星期二 38斤
2 草莓 张三 星期三 35斤
3 芒果 李四 星期一 20斤
4 芒果 李四 星期二 10斤
5 芒果 李四 星期三 18斤
6 桃子 王五 星期一 35斤
7 桃子 王五 星期二 25斤
8 桃子 王五 星期三 29斤
"""
# 字段名手动rename即可,这里就不写了
# 我们看到自动帮我们排序了,而且数据显然是对的
# 张三 星期一、二、三 卖草莓
# 李四 星期一、二、三 卖芒果
# 王五 星期一、二、三 卖桃子

pandas行转列、列转行、以及一行生成多行的更多相关文章

  1. SQL 行转列===列转行

    行转列:sum+if 在长表的数据组织结构中,同一uid对应了多行,即每门课程一条记录,对应一组分数,而在宽表中需要将其变成同一uid下仅对应一行 在长表中,仅有一列记录了课程成绩,但在宽表中则每门课 ...

  2. Pandas之容易让人混淆的行选择和列选择

    在刚学Pandas时,行选择和列选择非常容易混淆,在这里进行一下讨论和归纳 本文的数据来源:https://github.com/fivethirtyeight/data/tree/master/fa ...

  3. pandas数据处理基础——筛选指定行或者指定列的数据

    pandas主要的两个数据结构是:series(相当于一行或一列数据机构)和DataFrame(相当于多行多列的一个表格数据机构). 本文为了方便理解会与excel或者sql操作行或列来进行联想类比 ...

  4. SQL2005语句实现行转列,列转行

    在做报表时,经常需要将数据表中的行转列,或者列转行,如果不知道方法,你会觉得通过SQL语句来实现非常难.这里,我将使用pivot和unpivot来实现看似复杂的功能.这个功能在sql2005及以上版本 ...

  5. POI中getLastRowNum() 和getLastCellNum()的区别 hssfSheet.getLastRowNum();//最后一行行标,比行数小1 hssfSheet.getRow(k).getLastCellNum();//获取列数,比最后一列列标大1

    hssfSheet.getLastRowNum();//最后一行行标,比行数小1 hssfSheet.getRow(k).getLastCellNum();//获取列数,比最后一列列标大1

  6. HNU13028Attacking rooks (二分匹配,一行变多行,一列变多列)

    Attacking rooks Time Limit: 20000ms, Special Time Limit:50000ms, Memory Limit:65536KB Total submit u ...

  7. Pandas: 如何将一列中的文本拆分为多行? | Python

    Pandas: 如何将一列中的文本拆分为多行? 在数据处理过程中,经常会遇到以下类型的数据: 在同一列中,本该分别填入多行中的数据,被填在一行里了,然而在分析的时候,需要拆分成为多行. 在上图中,列名 ...

  8. SQL Server 行转列,列转行。多行转成一列

    一.多行转成一列(并以","隔开) 表名:A 表数据: 想要的查询结果: 查询语句: SELECT name , value = ( STUFF(( SELECT ',' + va ...

  9. SQL 行转列 列转行 PIVOT UNPIVOT

    1.基础表 2.行转列,注意ISNULL函数的使用,在总成绩的统计中,ISNULL(-,0) 有必要使用 3.列转行,对列语文.数学.英语.政治,进行列转行,转为了2列,score scname 这两 ...

随机推荐

  1. python判断命令执行成功

    if os.system('lss') !=0: print 'Without the command'

  2. 【Leetcode_easy】606. Construct String from Binary Tree

    problem 606. Construct String from Binary Tree 参考 1. Leetcode_easy_606. Construct String from Binary ...

  3. 转载【oracle切换表空间】

    http://blog.itpub.net/28939273/viewspace-1061476/ [root@yoon ~]# more /etc/oracle-releaseOracle Linu ...

  4. git 创建标签推送远程分支

    目录 git 创建标签推送远程分支 查看tag 创建tag 推送标签到远程仓库 删除tag git 创建标签推送远程分支 标签概念: tag, 对于迭代频繁的项目. 每一个标签可以理解为一个版本. 创 ...

  5. 在C/C++中常用的符号

    C++中&和*的用法一直是非常让人头疼的难点,课本博客上讲这些知识点一般都是分开讲其用法的,没有详细的总结,导致我在这方面的知识结构格外混乱,在网上找到了一篇英文文章简单总结了这两个符号的一些 ...

  6. 机器学习算法K-NN的一个使用实例:预测一个人是否患有糖尿病 (KNN-Predict whether a person will have diabetes or not )

    学习中...不断更新. 在糖尿病人的数据库中有几列是不能为0的 比如葡萄糖 胰岛素 身体指数和皮肤厚度.所以在数据预处理阶段需要对这些列的数据进行替换. remeber we did 12 minus ...

  7. IBM.WMQ订阅主题,连续获取消息解决办法

    去队列里面一直获取消息,一开始想到了两种解决方案: 第一:订阅一次获取一次消息,正常的话每次都能获取到,但是要及时去清理订阅并且时间粒度不好控制 第二:订阅一次,再获取消息这里加死循环,超时MQ已经做 ...

  8. Spring Maven工程引入css,js

    1.CSS <link th:href="@{/css/bootstrap.min.css}" rel="stylesheet" type="t ...

  9. PL/SQL链接Oracle数据库 导出表结构和表数据

    打开pl/sql客户端(导出数据表结构) 在左侧 点击tabales 2 Tools-->Export User Objects,导出sql格式的文件 3 红色1 是你要选择导出的表,红色2 是 ...

  10. 简单谈一谈Java内部类的使用原因

    使用内部类的原因 学习总得知其所以然,前面的一篇文章中我有提到过关于java内部类的几种用法以及一些示例,但是不明白内部类具体可以做什么,显然学习起来很渺茫,今天的文章简单说一说使用内部类的几个原因, ...