快速上手pandas(上)
pandas is a fast, powerful, flexible and easy to use open source data analysis and manipulation tool, built on top of the Python programming language.
pandas是一个灵活而强大的数据处理与数据分析工具集。它高度封装了NumPy
(高性能的N维数组运算库)、Matplotlib
(可视化工具)、文件读写等等,广泛应用于数据清洗、数据分析、数据挖掘等场景。
对NumPy
完全不了解的朋友,建议翻阅前文:
# 这里先导入下面会频繁使用到的模块
import numpy as np
import pandas as pd
import matplotlib.pyplot as plt
# 或者pd.show_versions()
pd.__version__
'1.1.3'
刚接触Python的朋友可能不知道help()
命令能随时查看帮助文档, 这里顺便提一下:
# help(np.random)
# help(pd)
# help(plt)
# help(pd.DataFrame)
# help参数也可以传入 实例对象的方法
# df = pd.DataFrame(np.random.randint(50, 100, (6, 5)))
# help(df.to_csv)
pandas中有三种数据结构,分别为:Series
(一维数据结构)、DataFrame
(二维表格型数据结构)和MultiIndex
(三维数据结构)。
Series is a one-dimensional labeled array capable of holding any data type (integers, strings, floating point numbers, Python objects, etc.). The axis labels are collectively referred to as the index.
Series是一个类似于一维数组的数据结构,主要由一组数据(data
)和与之相关的索引(index
)两部分构成。如下图所示:
下面看如何创建Series:
- 不指定index,使用默认index(0-N)
s1 = pd.Series([12, 4, 7, 9])
s1
0 12 1 4 2 7 3 9 dtype: int64
通过index来获取数据:
s1[0]
12
s1[3]
9
- 指定index
s2 = pd.Series([12, 4, 7, 9], index=["a", "b", "c", "d"])
s2
a 12 b 4 c 7 d 9 dtype: int64
通过index来获取数据:
s2['a']
12
s2['d']
9
Series can be instantiated from dicts
s3 = pd.Series({"d": 12, "c": 4, "b": 7, "a": 9})
s3
d 12 c 4 b 7 a 9 dtype: int64
When the data is a dict, and an index is not passed, the Series index will be ordered by the dict’s insertion order, if you’re using Python version >= 3.6 and pandas version >= 0.23.
通过index来获取数据:
s3['d']
12
s3['a']
9
Series也提供了两个属性index和values:
s1.index
RangeIndex(start=0, stop=4, step=1)
s2.index
Index(['a', 'b', 'c', 'd'], dtype='object')
s3.index
Index(['d', 'c', 'b', 'a'], dtype='object')
s3.values
array([12, 4, 7, 9])
If an index is passed, the values in data corresponding to the labels in the index will be pulled out.
pd.Series({"a": 0.0, "b": 1.0, "c": 2.0}, index=["b", "c", "d", "a"])
b 1.0 c 2.0 d NaN a 0.0 dtype: float64
NaN (not a number) is the standard missing data marker used in pandas.
注意:这里的NaN
是一个缺值标识。
如果data是一个标量,那么必须要传入index:
If data is a scalar value, an index must be provided. The value will be repeated to match the length of index.
pd.Series(5.0, index=["c", "a", "b"])
c 5.0 a 5.0 b 5.0 dtype: float64
其实上面的创建相当于:
pd.Series([5.0, 5.0, 5.0], index=["c", "a", "b"])
c 5.0 a 5.0 b 5.0 dtype: float64
ndarray上的一些操作对Series同样适用:
Series is ndarray-like. Series acts very similarly to a ndarray, and is a valid argument to most NumPy functions. However, operations such as slicing will also slice the index.
# 取s1前3个元素
s1[:3]
0 12 1 4 2 7 dtype: int64
# 哪些元素大于10
s1 > 10
0 True 1 False 2 False 3 False dtype: bool
更多操作请翻阅上文对NumPy中的ndarray的讲解。见:https://www.cnblogs.com/bytesfly/p/numpy.html
s1.dtype
dtype('int64')
If you need the actual array backing a Series, use Series.array
# Series转为array
s1.array
<PandasArray> [12, 4, 7, 9] Length: 4, dtype: int64
While Series is ndarray-like, if you need an actual ndarray, then use Series.to_numpy()
# Series转为ndarray
s1.to_numpy()
array([12, 4, 7, 9])
dict上的一些操作对Series也同样适用:
Series is dict-like. A Series is like a fixed-size dict in that you can get and set values by index label
s3['a'] = 100
s3
d 12 c 4 b 7 a 100 dtype: int64
"a" in s3
True
"y" in s3
False
# 获取不到给默认值NAN
s3.get("y", np.nan)
nan
DataFrame is a 2-dimensional labeled data structure with columns of potentially different types. You can think of it like a spreadsheet or SQL table, or a dict of Series objects. It is generally the most commonly used pandas object.
DataFrame是一个类似于二维数组或表的对象,既有行索引,又有列索引。如下图所示:
- 行索引(或者叫行标签),表明不同行,横向索引,叫index,0轴,axis=0
- 列索引(或者叫列标签),表名不同列,纵向索引,叫columns,1轴,axis=1
下面看如何创建DataFrame:
- 不指定行、列标签,默认使用0-N索引
# 随机生成6名学生,5门课程的分数
score = np.random.randint(50, 100, (6, 5))
# 创建DataFrame
pd.DataFrame(score)
0 | 1 | 2 | 3 | 4 | |
---|---|---|---|---|---|
0 | 69 | 90 | 56 | 97 | 79 |
1 | 57 | 98 | 70 | 57 | 82 |
2 | 63 | 66 | 98 | 78 | 63 |
3 | 74 | 58 | 75 | 57 | 68 |
4 | 94 | 78 | 83 | 72 | 73 |
5 | 60 | 73 | 62 | 72 | 79 |
- 指定行、列标签
# 列标签
subjects = ["语文", "数学", "英语", "物理", "化学"]
# 行标签
stus = ['学生' + str(i+1) for i in range(score.shape[0])]
# 创建DataFrame
score_df = pd.DataFrame(score, columns=subjects, index=stus)
score_df
语文 | 数学 | 英语 | 物理 | 化学 | |
---|---|---|---|---|---|
学生1 | 69 | 90 | 56 | 97 | 79 |
学生2 | 57 | 98 | 70 | 57 | 82 |
学生3 | 63 | 66 | 98 | 78 | 63 |
学生4 | 74 | 58 | 75 | 57 | 68 |
学生5 | 94 | 78 | 83 | 72 | 73 |
学生6 | 60 | 73 | 62 | 72 | 79 |
加了行列标签后,显然数据可读性更强了,一目了然。
同样再看DataFrame的几个基本属性:
score_df.shape
(6, 5)
score_df.columns
Index(['语文', '数学', '英语', '物理', '化学'], dtype='object')
score_df.index
Index(['学生1', '学生2', '学生3', '学生4', '学生5', '学生6'], dtype='object')
score_df.values
array([[69, 90, 56, 97, 79], [57, 98, 70, 57, 82], [63, 66, 98, 78, 63], [74, 58, 75, 57, 68], [94, 78, 83, 72, 73], [60, 73, 62, 72, 79]])
# 转置
score_df.T
学生1 | 学生2 | 学生3 | 学生4 | 学生5 | 学生6 | |
---|---|---|---|---|---|---|
语文 | 69 | 57 | 63 | 74 | 94 | 60 |
数学 | 90 | 98 | 66 | 58 | 78 | 73 |
英语 | 56 | 70 | 98 | 75 | 83 | 62 |
物理 | 97 | 57 | 78 | 57 | 72 | 72 |
化学 | 79 | 82 | 63 | 68 | 73 | 79 |
# 显示前3行内容
score_df.head(3)
语文 | 数学 | 英语 | 物理 | 化学 | |
---|---|---|---|---|---|
学生1 | 69 | 90 | 56 | 97 | 79 |
学生2 | 57 | 98 | 70 | 57 | 82 |
学生3 | 63 | 66 | 98 | 78 | 63 |
# 显示后3行内容
score_df.tail(3)
语文 | 数学 | 英语 | 物理 | 化学 | |
---|---|---|---|---|---|
学生4 | 74 | 58 | 75 | 57 | 68 |
学生5 | 94 | 78 | 83 | 72 | 73 |
学生6 | 60 | 73 | 62 | 72 | 79 |
修改行标签:
stus = ['stu' + str(i+1) for i in range(score.shape[0])]
score_df.index = stus
score_df
语文 | 数学 | 英语 | 物理 | 化学 | |
---|---|---|---|---|---|
stu1 | 69 | 90 | 56 | 97 | 79 |
stu2 | 57 | 98 | 70 | 57 | 82 |
stu3 | 63 | 66 | 98 | 78 | 63 |
stu4 | 74 | 58 | 75 | 57 | 68 |
stu5 | 94 | 78 | 83 | 72 | 73 |
stu6 | 60 | 73 | 62 | 72 | 79 |
重置行标签:
# drop默认为False,不删除原来的索引值
score_df.reset_index()
index | 语文 | 数学 | 英语 | 物理 | 化学 | |
---|---|---|---|---|---|---|
0 | stu1 | 69 | 90 | 56 | 97 | 79 |
1 | stu2 | 57 | 98 | 70 | 57 | 82 |
2 | stu3 | 63 | 66 | 98 | 78 | 63 |
3 | stu4 | 74 | 58 | 75 | 57 | 68 |
4 | stu5 | 94 | 78 | 83 | 72 | 73 |
5 | stu6 | 60 | 73 | 62 | 72 | 79 |
# drop为True, 则删除原来的索引值
score_df.reset_index(drop=True)
语文 | 数学 | 英语 | 物理 | 化学 | |
---|---|---|---|---|---|
0 | 69 | 90 | 56 | 97 | 79 |
1 | 57 | 98 | 70 | 57 | 82 |
2 | 63 | 66 | 98 | 78 | 63 |
3 | 74 | 58 | 75 | 57 | 68 |
4 | 94 | 78 | 83 | 72 | 73 |
5 | 60 | 73 | 62 | 72 | 79 |
score_df
语文 | 数学 | 英语 | 物理 | 化学 | |
---|---|---|---|---|---|
stu1 | 69 | 90 | 56 | 97 | 79 |
stu2 | 57 | 98 | 70 | 57 | 82 |
stu3 | 63 | 66 | 98 | 78 | 63 |
stu4 | 74 | 58 | 75 | 57 | 68 |
stu5 | 94 | 78 | 83 | 72 | 73 |
stu6 | 60 | 73 | 62 | 72 | 79 |
将某列值设置为新的索引:
set_index(keys, drop=True)
- keys : 列索引名成或者列索引名称的列表
- drop : boolean, default True.当做新的索引,删除原来的列
hero_df = pd.DataFrame({'id': [1, 2, 3, 4, 5],
'name': ['李寻欢', '令狐冲', '张无忌', '郭靖', '花无缺'],
'book': ['多情剑客无情剑', '笑傲江湖', '倚天屠龙记', '射雕英雄传', '绝代双骄'],
'skill': ['小李飞刀', '独孤九剑', '九阳神功', '降龙十八掌', '移花接玉']})
hero_df.set_index('id')
name | book | skill | |
---|---|---|---|
id | |||
1 | 李寻欢 | 多情剑客无情剑 | 小李飞刀 |
2 | 令狐冲 | 笑傲江湖 | 独孤九剑 |
3 | 张无忌 | 倚天屠龙记 | 九阳神功 |
4 | 郭靖 | 射雕英雄传 | 降龙十八掌 |
5 | 花无缺 | 绝代双骄 | 移花接玉 |
设置多个索引,以id
和name
:
df = hero_df.set_index(['id', 'name'])
df
book | skill | ||
---|---|---|---|
id | name | ||
1 | 李寻欢 | 多情剑客无情剑 | 小李飞刀 |
2 | 令狐冲 | 笑傲江湖 | 独孤九剑 |
3 | 张无忌 | 倚天屠龙记 | 九阳神功 |
4 | 郭靖 | 射雕英雄传 | 降龙十八掌 |
5 | 花无缺 | 绝代双骄 | 移花接玉 |
df.index
MultiIndex([(1, '李寻欢'), (2, '令狐冲'), (3, '张无忌'), (4, '郭靖'), (5, '花无缺')], names=['id', 'name'])
此时df就是一个具有MultiIndex
的DataFrame
。
df.index.names
FrozenList(['id', 'name'])
df.index.levels
FrozenList([[1, 2, 3, 4, 5], ['令狐冲', '张无忌', '李寻欢', '花无缺', '郭靖']])
df
book | skill | ||
---|---|---|---|
id | name | ||
1 | 李寻欢 | 多情剑客无情剑 | 小李飞刀 |
2 | 令狐冲 | 笑傲江湖 | 独孤九剑 |
3 | 张无忌 | 倚天屠龙记 | 九阳神功 |
4 | 郭靖 | 射雕英雄传 | 降龙十八掌 |
5 | 花无缺 | 绝代双骄 | 移花接玉 |
- 直接使用行列索引(先列后行):
df['skill']
id name 1 李寻欢 小李飞刀 2 令狐冲 独孤九剑 3 张无忌 九阳神功 4 郭靖 降龙十八掌 5 花无缺 移花接玉 Name: skill, dtype: object
df['skill'][1]
name 李寻欢 小李飞刀 Name: skill, dtype: object
df['skill'][1]['李寻欢']
'小李飞刀'
- 使用
loc
(指定行列索引的名字)
df.loc[1:3]
book | skill | ||
---|---|---|---|
id | name | ||
1 | 李寻欢 | 多情剑客无情剑 | 小李飞刀 |
2 | 令狐冲 | 笑傲江湖 | 独孤九剑 |
3 | 张无忌 | 倚天屠龙记 | 九阳神功 |
df.loc[(2, '令狐冲'):(4, '郭靖')]
book | skill | ||
---|---|---|---|
id | name | ||
2 | 令狐冲 | 笑傲江湖 | 独孤九剑 |
3 | 张无忌 | 倚天屠龙记 | 九阳神功 |
4 | 郭靖 | 射雕英雄传 | 降龙十八掌 |
df.loc[1:3, 'book']
id name 1 李寻欢 多情剑客无情剑 2 令狐冲 笑傲江湖 3 张无忌 倚天屠龙记 Name: book, dtype: object
df.loc[df.index[1:3], ['book', 'skill']]
book | skill | ||
---|---|---|---|
id | name | ||
2 | 令狐冲 | 笑傲江湖 | 独孤九剑 |
3 | 张无忌 | 倚天屠龙记 | 九阳神功 |
- 使用iloc(通过索引的下标)
# 获取前2行数据
df.iloc[:2]
book | skill | ||
---|---|---|---|
id | name | ||
1 | 李寻欢 | 多情剑客无情剑 | 小李飞刀 |
2 | 令狐冲 | 笑傲江湖 | 独孤九剑 |
df.iloc[0:2, df.columns.get_indexer(['skill'])]
skill | ||
---|---|---|
id | name | |
1 | 李寻欢 | 小李飞刀 |
2 | 令狐冲 | 独孤九剑 |
# 添加新列
df['score'] = 100
df['gender'] = 'male'
df
book | skill | score | gender | ||
---|---|---|---|---|---|
id | name | ||||
1 | 李寻欢 | 多情剑客无情剑 | 小李飞刀 | 100 | male |
2 | 令狐冲 | 笑傲江湖 | 独孤九剑 | 100 | male |
3 | 张无忌 | 倚天屠龙记 | 九阳神功 | 100 | male |
4 | 郭靖 | 射雕英雄传 | 降龙十八掌 | 100 | male |
5 | 花无缺 | 绝代双骄 | 移花接玉 | 100 | male |
# 修改列值
df['score'] = 99
df
book | skill | score | gender | ||
---|---|---|---|---|---|
id | name | ||||
1 | 李寻欢 | 多情剑客无情剑 | 小李飞刀 | 99 | male |
2 | 令狐冲 | 笑傲江湖 | 独孤九剑 | 99 | male |
3 | 张无忌 | 倚天屠龙记 | 九阳神功 | 99 | male |
4 | 郭靖 | 射雕英雄传 | 降龙十八掌 | 99 | male |
5 | 花无缺 | 绝代双骄 | 移花接玉 | 99 | male |
# 或者这样修改列值
df.score = 100
df
book | skill | score | gender | ||
---|---|---|---|---|---|
id | name | ||||
1 | 李寻欢 | 多情剑客无情剑 | 小李飞刀 | 100 | male |
2 | 令狐冲 | 笑傲江湖 | 独孤九剑 | 100 | male |
3 | 张无忌 | 倚天屠龙记 | 九阳神功 | 100 | male |
4 | 郭靖 | 射雕英雄传 | 降龙十八掌 | 100 | male |
5 | 花无缺 | 绝代双骄 | 移花接玉 | 100 | male |
- sort_index
# 按索引降序
df.sort_index(ascending=False)
book | skill | score | gender | ||
---|---|---|---|---|---|
id | name | ||||
5 | 花无缺 | 绝代双骄 | 移花接玉 | 100 | male |
4 | 郭靖 | 射雕英雄传 | 降龙十八掌 | 100 | male |
3 | 张无忌 | 倚天屠龙记 | 九阳神功 | 100 | male |
2 | 令狐冲 | 笑傲江湖 | 独孤九剑 | 100 | male |
1 | 李寻欢 | 多情剑客无情剑 | 小李飞刀 | 100 | male |
- sort_values
先把score
设置为不同的值:
df['score'][1]['李寻欢'] = 80
df['score'][2]['令狐冲'] = 96
df['score'][3]['张无忌'] = 86
df['score'][4]['郭靖'] = 99
df['score'][5]['花无缺'] = 95
df
book | skill | score | gender | ||
---|---|---|---|---|---|
id | name | ||||
1 | 李寻欢 | 多情剑客无情剑 | 小李飞刀 | 80 | male |
2 | 令狐冲 | 笑傲江湖 | 独孤九剑 | 96 | male |
3 | 张无忌 | 倚天屠龙记 | 九阳神功 | 86 | male |
4 | 郭靖 | 射雕英雄传 | 降龙十八掌 | 99 | male |
5 | 花无缺 | 绝代双骄 | 移花接玉 | 95 | male |
# 按照score降序
df.sort_values(by='score', ascending=False)
book | skill | score | gender | ||
---|---|---|---|---|---|
id | name | ||||
4 | 郭靖 | 射雕英雄传 | 降龙十八掌 | 99 | male |
2 | 令狐冲 | 笑傲江湖 | 独孤九剑 | 96 | male |
5 | 花无缺 | 绝代双骄 | 移花接玉 | 95 | male |
3 | 张无忌 | 倚天屠龙记 | 九阳神功 | 86 | male |
1 | 李寻欢 | 多情剑客无情剑 | 小李飞刀 | 80 | male |
# 按照book名称字符串长度 升序
df.sort_values(by='book', key=lambda col: col.str.len())
book | skill | score | gender | ||
---|---|---|---|---|---|
id | name | ||||
2 | 令狐冲 | 笑傲江湖 | 独孤九剑 | 96 | male |
5 | 花无缺 | 绝代双骄 | 移花接玉 | 95 | male |
3 | 张无忌 | 倚天屠龙记 | 九阳神功 | 86 | male |
4 | 郭靖 | 射雕英雄传 | 降龙十八掌 | 99 | male |
1 | 李寻欢 | 多情剑客无情剑 | 小李飞刀 | 80 | male |
# score+1
df['score'].add(1)
id name 1 李寻欢 81 2 令狐冲 97 3 张无忌 87 4 郭靖 100 5 花无缺 96 Name: score, dtype: int64
# score-1
df['score'].sub(1)
id name 1 李寻欢 79 2 令狐冲 95 3 张无忌 85 4 郭靖 98 5 花无缺 94 Name: score, dtype: int64
# 或者直接用 + - * / // %等运算符
(df['score'] + 1) % 10
id name 1 李寻欢 1 2 令狐冲 7 3 张无忌 7 4 郭靖 0 5 花无缺 6 Name: score, dtype: int64
先回顾一下数据内容:
df
book | skill | score | gender | ||
---|---|---|---|---|---|
id | name | ||||
1 | 李寻欢 | 多情剑客无情剑 | 小李飞刀 | 80 | male |
2 | 令狐冲 | 笑傲江湖 | 独孤九剑 | 96 | male |
3 | 张无忌 | 倚天屠龙记 | 九阳神功 | 86 | male |
4 | 郭靖 | 射雕英雄传 | 降龙十八掌 | 99 | male |
5 | 花无缺 | 绝代双骄 | 移花接玉 | 95 | male |
逻辑运算结果:
df['score'] > 90
id name 1 李寻欢 False 2 令狐冲 True 3 张无忌 False 4 郭靖 True 5 花无缺 True Name: score, dtype: bool
# 筛选出分数大于90的行
df[df['score'] > 90]
book | skill | score | gender | ||
---|---|---|---|---|---|
id | name | ||||
2 | 令狐冲 | 笑傲江湖 | 独孤九剑 | 96 | male |
4 | 郭靖 | 射雕英雄传 | 降龙十八掌 | 99 | male |
5 | 花无缺 | 绝代双骄 | 移花接玉 | 95 | male |
# 筛选出分数在85到90之间的行
df[(df['score'] > 85) & (df['score'] < 90)]
book | skill | score | gender | ||
---|---|---|---|---|---|
id | name | ||||
3 | 张无忌 | 倚天屠龙记 | 九阳神功 | 86 | male |
# 筛选出分数在85以下或者95以上的行
df[(df['score'] < 85) | (df['score'] > 95)]
book | skill | score | gender | ||
---|---|---|---|---|---|
id | name | ||||
1 | 李寻欢 | 多情剑客无情剑 | 小李飞刀 | 80 | male |
2 | 令狐冲 | 笑傲江湖 | 独孤九剑 | 96 | male |
4 | 郭靖 | 射雕英雄传 | 降龙十八掌 | 99 | male |
或者通过query()
函数实现上面的需求:
df.query("score<85 | score>95")
book | skill | score | gender | ||
---|---|---|---|---|---|
id | name | ||||
1 | 李寻欢 | 多情剑客无情剑 | 小李飞刀 | 80 | male |
2 | 令狐冲 | 笑傲江湖 | 独孤九剑 | 96 | male |
4 | 郭靖 | 射雕英雄传 | 降龙十八掌 | 99 | male |
此外,可以用isin(values)
函数来筛选指定的值,类似于SQL
中in
查询:
df[df["score"].isin([99, 96])]
book | skill | score | gender | ||
---|---|---|---|---|---|
id | name | ||||
2 | 令狐冲 | 笑傲江湖 | 独孤九剑 | 96 | male |
4 | 郭靖 | 射雕英雄传 | 降龙十八掌 | 99 | male |
describe()
能够直接得出很多统计结果:count, mean, std, min, max 等:
df.describe()
score | |
---|---|
count | 5.000000 |
mean | 91.200000 |
std | 7.918333 |
min | 80.000000 |
25% | 86.000000 |
50% | 95.000000 |
75% | 96.000000 |
max | 99.000000 |
# 使用统计函数:axis=0代表求列统计结果,1代表求行统计结果
df.max(axis=0, numeric_only=True)
score 99 dtype: int64
其他几个常用的聚合函数都类似。不再一一举例。
下面重点看下累计统计函数。
函数 | 作用 |
---|---|
cumsum | 计算前1/2/3/…/n个数的和 |
cummax | 计算前1/2/3/…/n个数的最大值 |
cummin | 计算前1/2/3/…/n个数的最小值 |
cumprod | 计算前1/2/3/…/n个数的积 |
下面是某公司近半年以来的各部门的营业收入数据:
income = pd.DataFrame(data=np.random.randint(60, 100, (6, 5)),
columns=['group' + str(x) for x in range(1, 6)],
index=['Month' + str(x) for x in range(1, 7)])
income
group1 | group2 | group3 | group4 | group5 | |
---|---|---|---|---|---|
Month1 | 97 | 89 | 62 | 82 | 71 |
Month2 | 68 | 69 | 82 | 66 | 79 |
Month3 | 77 | 87 | 66 | 94 | 82 |
Month4 | 69 | 76 | 99 | 79 | 61 |
Month5 | 77 | 94 | 76 | 70 | 70 |
Month6 | 89 | 64 | 92 | 63 | 60 |
统计group1
的前N个月的总营业收入:
group1_income = income['group1']
group1_income.cumsum()
Month1 97 Month2 165 Month3 242 Month4 311 Month5 388 Month6 477 Name: group1, dtype: int64
用图形展示会更加直观:
group1_income.cumsum().plot(figsize=(8, 5))
plt.show()
同理,统计group1
的前N个月的最大营业收入:
group1_income.cummax().plot(figsize=(8, 5))
plt.show()
先看下近半年以来前3个部门的营业收入数据:
income[['group1', 'group2', 'group3']]
group1 | group2 | group3 | |
---|---|---|---|
Month1 | 97 | 89 | 62 |
Month2 | 68 | 69 | 82 |
Month3 | 77 | 87 | 66 |
Month4 | 69 | 76 | 99 |
Month5 | 77 | 94 | 76 |
Month6 | 89 | 64 | 92 |
# 近半年 前3个部门 每月营业收入极差
income[['group1', 'group2', 'group3']].apply(lambda x: x.max() - x.min(), axis=1)
Month1 35 Month2 14 Month3 21 Month4 30 Month5 18 Month6 28 dtype: int64
# 近半年 前3个部门 每个部门营业收入极差
income[['group1', 'group2', 'group3']].apply(lambda x: x.max() - x.min(), axis=0)
group1 29 group2 30 group3 37 dtype: int64
The pandas I/O API is a set of top level reader functions accessed like pandas.read_csv() that generally return a pandas object. The corresponding writer functions are object methods that are accessed like DataFrame.to_csv(). Below is a table containing available readers and writers.
Format Type | Data Description | Reader | Writer |
---|---|---|---|
text | CSV | read_csv | to_csv |
text | Fixed-Width Text File | read_fwf | |
text | JSON | read_json | to_json |
text | HTML | read_html | to_html |
text | Local clipboard | read_clipboard | to_clipboard |
binary | MS Excel | read_excel | to_excel |
binary | OpenDocument | read_excel | |
binary | HDF5 Format | read_hdf | to_hdf |
binary | Feather Format | read_feather | to_feather |
binary | Parquet Format | read_parquet | to_parquet |
binary | ORC Format | read_orc | |
binary | Msgpack | read_msgpack | to_msgpack |
binary | Stata | read_stata | to_stata |
binary | SAS | read_sas | |
binary | SPSS | read_spss | |
binary | Python Pickle Format | read_pickle | to_pickle |
SQL | SQL | read_sql | to_sql |
SQL | Google BigQuery | read_gbq | to_gbq |
这里用下面网址的csv数据来做一些测试:
https://www.stats.govt.nz/large-datasets/csv-files-for-download/
path = "https://www.stats.govt.nz/assets/Uploads/Employment-indicators/Employment-indicators-Weekly-as-at-24-May-2021/Download-data/Employment-indicators-weekly-paid-jobs-20-days-as-at-24-May-2021.csv"
# 读csv
data = pd.read_csv(path, sep=',', usecols=['Week_end', 'High_industry', 'Value'])
data
Week_end | High_industry | Value | |
---|---|---|---|
0 | 2019-05-05 | Total | 1828160.00 |
1 | 2019-05-05 | A Primary | 79880.00 |
2 | 2019-05-05 | B Goods Producing | 344320.00 |
3 | 2019-05-05 | C Services | 1389220.00 |
4 | 2019-05-05 | Z No Match | 14730.00 |
... | ... | ... | ... |
2095 | 2021-05-02 | Total | 700.94 |
2096 | 2021-05-02 | A Primary | 680.20 |
2097 | 2021-05-02 | B Goods Producing | 916.42 |
2098 | 2021-05-02 | C Services | 649.92 |
2099 | 2021-05-02 | Z No Match | 425.65 |
2100 rows × 3 columns
# 写csv
data[:20].to_csv("./test.csv", columns=['Week_end', 'Value'],
header=True, index=False, mode='w')
输出如下:
Week_end,Value
2019-05-05,1828160.0
2019-05-05,79880.0
2019-05-05,344320.0
更多Json格式数据,google关键词site:api.androidhive.info
path = "https://api.androidhive.info/json/movies.json"
# 读json
data = pd.read_json(path)
data = data.loc[:2, ['title', 'rating']]
data
title | rating | |
---|---|---|
0 | Dawn of the Planet of the Apes | 8.3 |
1 | District 9 | 8.0 |
2 | Transformers: Age of Extinction | 6.3 |
- records
data.to_json("./test.json", orient='records', lines=True)
输出如下:
{"title":"Dawn of the Planet of the Apes","rating":8.3}
{"title":"District 9","rating":8.0}
{"title":"Transformers: Age of Extinction","rating":6.3}
如果lines=False
,即:
data.to_json("./test.json", orient='records', lines=False)
输出如下:
[
{
"title":"Dawn of the Planet of the Apes",
"rating":8.3
},
{
"title":"District 9",
"rating":"8.0"
},
{
"title":"Transformers: Age of Extinction",
"rating":6.3
}
]
- columns
data.to_json("./test.json", orient='columns')
输出如下:
{
"title":{
"0":"Dawn of the Planet of the Apes",
"1":"District 9",
"2":"Transformers: Age of Extinction"
},
"rating":{
"0":8.3,
"1":"8.0",
"2":6.3
}
}
- index
data.to_json("./test.json", orient='index')
输出如下:
{
"0":{
"title":"Dawn of the Planet of the Apes",
"rating":8.3
},
"1":{
"title":"District 9",
"rating":"8.0"
},
"2":{
"title":"Transformers: Age of Extinction",
"rating":6.3
}
}
- split
data.to_json("./test.json", orient='split')
输出如下:
{
"columns":[
"title",
"rating"
],
"index":[
0,
1,
2
],
"data":[
[
"Dawn of the Planet of the Apes",
8.3
],
[
"District 9",
"8.0"
],
[
"Transformers: Age of Extinction",
6.3
]
]
}
- values
data.to_json("./test.json", orient='values')
输出如下:
[
[
"Dawn of the Planet of the Apes",
8.3
],
[
"District 9",
"8.0"
],
[
"Transformers: Age of Extinction",
6.3
]
]
# 读Excel
team = pd.read_excel('https://www.gairuo.com/file/data/dataset/team.xlsx')
team.head(5)
name | team | Q1 | Q2 | Q3 | Q4 | |
---|---|---|---|---|---|---|
0 | Liver | E | 89 | 21 | 24 | 64 |
1 | Arry | C | 36 | 37 | 37 | 57 |
2 | Ack | A | 57 | 60 | 18 | 84 |
3 | Eorge | C | 93 | 96 | 71 | 78 |
4 | Oah | D | 65 | 49 | 61 | 86 |
# 末尾添加一列sum, 值为Q1、Q2、Q3、Q4列的和
team['sum'] = team['Q1'] + team['Q2'] + team['Q3'] + team['Q4']
team.head(5)
name | team | Q1 | Q2 | Q3 | Q4 | sum | |
---|---|---|---|---|---|---|---|
0 | Liver | E | 89 | 21 | 24 | 64 | 198 |
1 | Arry | C | 36 | 37 | 37 | 57 | 167 |
2 | Ack | A | 57 | 60 | 18 | 84 | 219 |
3 | Eorge | C | 93 | 96 | 71 | 78 | 338 |
4 | Oah | D | 65 | 49 | 61 | 86 | 261 |
# 写Excel
team.to_excel('test.xlsx', index=False)
HDF5(Hierarchical Data Format
)是用于存储大规模数值数据的较为理想的存储格式。
优势:
- HDF5在存储的时候支持压缩,从而提磁盘利用率,节省空间
- HDF5跨平台的,可轻松迁移到Hadoop上
score = pd.DataFrame(np.random.randint(50, 100, (100000, 10)))
score.head()
0 | 1 | 2 | 3 | 4 | 5 | 6 | 7 | 8 | 9 | |
---|---|---|---|---|---|---|---|---|---|---|
0 | 76 | 53 | 51 | 94 | 84 | 77 | 56 | 82 | 56 | 50 |
1 | 93 | 59 | 84 | 83 | 77 | 67 | 52 | 52 | 53 | 62 |
2 | 96 | 98 | 88 | 72 | 96 | 64 | 58 | 67 | 89 | 95 |
3 | 57 | 75 | 89 | 73 | 72 | 73 | 58 | 93 | 72 | 92 |
4 | 50 | 50 | 52 | 57 | 72 | 76 | 78 | 52 | 90 | 93 |
# 写HDF5
score.to_hdf("./score.h5", key="score", complevel=9, mode='w')
# 读HDF5
new_score = pd.read_hdf("./score.h5", key="score")
new_score.head()
0 | 1 | 2 | 3 | 4 | 5 | 6 | 7 | 8 | 9 | |
---|---|---|---|---|---|---|---|---|---|---|
0 | 76 | 53 | 51 | 94 | 84 | 77 | 56 | 82 | 56 | 50 |
1 | 93 | 59 | 84 | 83 | 77 | 67 | 52 | 52 | 53 | 62 |
2 | 96 | 98 | 88 | 72 | 96 | 64 | 58 | 67 | 89 | 95 |
3 | 57 | 75 | 89 | 73 | 72 | 73 | 58 | 93 | 72 | 92 |
4 | 50 | 50 | 52 | 57 | 72 | 76 | 78 | 52 | 90 | 93 |
注意:HDF5文件的读取和存储需要指定一个键(key
),值为要存储的DataFrame
。
score.to_csv("./score.csv", mode='w')
同时,来对比一下写HDF5与写csv的占用磁盘情况:
-rw-r--r-- 1 wind staff 3.4M 6 13 10:24 score.csv
-rw-r--r-- 1 wind staff 763K 6 13 10:23 score.h5
其他格式的文件读写也都类似,这里不再举例说明。
Pandas的DataFrame
和Series
,在matplotlib
基础上封装了一个简易的绘图函数, 使得在数据处理过程中可以方便快速可视化数据。
相关文档:
折线图用于分析事物随时间或有序类别而变化的趋势。
先来个快速入门的案例,绘制sin(x)
的一个周期内(0, 2π)
的函数图像:
# 中文字体
plt.rc('font', family='Arial Unicode MS')
plt.rc('axes', unicode_minus='False')
x = np.arange(0, 6.29, 0.01)
y = np.sin(x)
s = pd.Series(y, index=x)
s.plot(kind='line', title='sin(x)图像',
style='--g', grid=True, figsize=(8, 6))
plt.show()
注意:中文显示有问题的,可用如下代码查看系统可用字体。
from matplotlib.font_manager import FontManager
# fonts = set([x.name for x in FontManager().ttflist])
# print(fonts)
如果要绘制DataFrame
数据的某2列数据的折线图,传入x='列1',y='列2',就能得到以'列1'为x轴,'列2'为y轴的线型图。如果没有指明x,x轴默认用index
。如下例子:
df = pd.DataFrame({'x': x, 'y': y})
df.plot(x='x', y='y', kind='line', ylabel='y=sin(x)',
style='--g', grid=True, figsize=(8, 6))
plt.show()
再看一个实际的案例。下面是北京、上海、合肥三个城市某天中午气温的数据。
x = [f'12点{i}分' for i in range(60)]
y_beijing = np.random.uniform(18, 23, len(x))
y_shanghai = np.random.uniform(23, 26, len(x))
y_hefei = np.random.uniform(21, 28, len(x))
df = pd.DataFrame({'x': x, 'beijing': y_beijing, 'shanghai': y_shanghai, 'hefei': y_hefei})
df.head(5)
x | beijing | shanghai | hefei | |
---|---|---|---|---|
0 | 12点0分 | 21.516230 | 24.627902 | 25.232748 |
1 | 12点1分 | 22.601501 | 25.982331 | 26.403320 |
2 | 12点2分 | 19.739455 | 23.602787 | 25.569943 |
3 | 12点3分 | 21.741182 | 25.102164 | 24.400619 |
4 | 12点4分 | 19.888968 | 23.995114 | 22.879671 |
绘制北京、上海、合肥三个城市气温随时间变化的情况如下:
df.plot(x='x', y=['beijing', 'shanghai', 'hefei'], kind='line',
figsize=(12, 6), xlabel='时间', ylabel='温度')
plt.show()
添加参数subplots=True
, 可以形成多个子图,如下:
df.plot(x='x', y=['beijing', 'shanghai', 'hefei'], kind='line',
subplots=True, figsize=(12, 6), xlabel='时间', ylabel='温度')
plt.show()
另外,参数layout=(m,n)
可以指明子图的行列数,期中m*n
的值要大于子图的数量,如下:
df.plot(x='x', y=['beijing', 'shanghai', 'hefei'], kind='line',
subplots=True, layout=(2, 2), figsize=(12, 6), xlabel='时间', ylabel='温度')
plt.show()
柱状图最适合对分类的数据进行比较。
武林大会上,每个英雄参加10个回合的比拼,每人 胜局(wins
)、平局(draws
)、败局(losses
)的统计如下:
columns = ['hero', 'wins', 'draws', 'losses']
score = [
['李寻欢', 6, 1, 3],
['令狐冲', 5, 4, 1],
['张无忌', 5, 3, 2],
['郭靖', 4, 5, 1],
['花无缺', 5, 2, 3]
]
df = pd.DataFrame(score, columns=columns)
df
hero | wins | draws | losses | |
---|---|---|---|---|
0 | 李寻欢 | 6 | 1 | 3 |
1 | 令狐冲 | 5 | 4 | 1 |
2 | 张无忌 | 5 | 3 | 2 |
3 | 郭靖 | 4 | 5 | 1 |
4 | 花无缺 | 5 | 2 | 3 |
df.plot(kind='bar', x='hero', y=['wins', 'draws', 'losses'],
rot=0, title='柱状图', xlabel='', figsize=(10, 6))
plt.show()
添加stacked=True
可以绘制堆叠柱状图,如下:
df.plot(kind='bar', x='hero', y=['wins', 'draws', 'losses'],
stacked=True, rot=0, title='堆叠柱状图', xlabel='', figsize=(10, 6))
plt.show()
另外使用plot(kind='barh')
或者plot.barh()
可以绘制水平柱状图。下面rot
参数设置x标签的旋转角度,alpha
设置透明度,align
设置对齐位置。
df.plot(kind='barh', x='hero', y=['wins'], xlabel='',
align='center', alpha=0.8, rot=0, title='水平柱状图', figsize=(10, 6))
plt.show()
同样添加参数subplots=True
,可以形成多个子图,如下:
df.plot(kind='bar', x='hero', y=['wins', 'draws', 'losses'],
subplots=True, rot=0, xlabel='', figsize=(10, 8))
plt.show()
饼图最显著的功能在于表现“占比”。
每当某些机构或者平台发布编程语言排行榜以及市场占有率时,相信行业内的很多朋友会很自然地瞄上几眼。
这里举个某互联网公司研发部使用的后端编程语言占比:
colors = ['#FF6600', '#0099FF', '#FFFF00', '#FF0066', '#339900']
language = ['Java', 'Python', 'Golang', 'Scala', 'Others']
s = pd.Series([0.5, 0.3, 0.1, 0.06, 0.04], name='language',
index=language)
s.plot(kind='pie', figsize=(7, 7),
autopct="%.0f%%", colors=colors)
plt.show()
散点图适用于分析变量之间是否存在某种关系或相关性。
先造一些随机数据,如下:
x = np.random.uniform(0, 100, 100)
y = [2*n for n in x]
df = pd.DataFrame({'x': x, 'y': y})
df.head(5)
x | y | |
---|---|---|
0 | 10.405567 | 20.811134 |
1 | 24.520765 | 49.041530 |
2 | 32.735258 | 65.470516 |
3 | 90.868823 | 181.737646 |
4 | 21.875188 | 43.750377 |
绘制散点图,如下:
df.plot(kind='scatter', x='x', y='y', figsize=(12, 6))
plt.show()
从图中,可以看出y与x可能存在正相关的关系。
直方图用于表示数据的分布情况。一般用横轴表示数据区间,纵轴表示分布情况,柱子越高,则落在该区间的数量越大。
构建直方图,首先要确定“组距”、对数值的范围进行分区,通俗的说即是划定有几根柱子(例如0-100分,每隔20分划一个区间,共5个区间)。接着,对落在每个区间的数值进行频次计算(如落在80-100分的10人,60-80分的20人,以此类推)。 最后,绘制矩形,高度由频数决定。
注意:直方图并不等于柱状图,不能对离散的分类数据进行比较。
在前文 快速上手NumPy 中,简单讲过正态分布,也画过正态分布的直方图。下面看用pandas如何画直方图:
已知某地区成年男性身高近似服从正态分布。下面生成均值为170,标准差为5的100000个符合正态分布规律的样本数据。
height = np.random.normal(170, 5, 100000)
df = pd.DataFrame({'height': height})
df.head(5)
height | |
---|---|
0 | 166.547946 |
1 | 166.847060 |
2 | 166.887866 |
3 | 175.607073 |
4 | 181.527058 |
绘制直方图,其中分组数为100:
df.plot(kind='hist', bins=100, figsize=(10, 5))
plt.grid(True, linestyle='--', alpha=0.8)
plt.show()
从图中可以直观地看出,大多人身高集中在170左右。
箱形图多用于数值统计,它不需要占据过多的画布空间,空间利用率高,非常适用于比较多组数据的分布情况。通过箱形图,可以很快知道一些关键的统计值,如最大值、最小值、中位数、上下四分位数等等。
某班级30个学生在期末考试中,语文、数学、英语、物理、化学5门课的成绩数据如下:
count = 30
chinese_score = np.random.normal(80, 10, count)
maths_score = np.random.normal(85, 20, count)
english_score = np.random.normal(70, 25, count)
physics_score = np.random.normal(65, 30, count)
chemistry_score = np.random.normal(75, 5, count)
scores = pd.DataFrame({'Chinese': chinese_score, 'Maths': maths_score, 'English': english_score,
'Physics': physics_score, 'Chemistry': chemistry_score})
scores.head(5)
Chinese | Maths | English | Physics | Chemistry | |
---|---|---|---|---|---|
0 | 81.985202 | 77.139479 | 78.881483 | 98.688823 | 75.849040 |
1 | 71.597557 | 69.960533 | 98.784664 | 72.140422 | 73.179419 |
2 | 80.581071 | 75.501743 | 99.491803 | 18.579709 | 67.963091 |
3 | 82.396994 | 113.018430 | 83.224544 | 38.406359 | 75.713590 |
4 | 83.153492 | 103.378598 | 59.399535 | 78.381277 | 77.020693 |
绘制箱形图如下:
scores.plot(kind='box', figsize=(10, 6))
plt.grid(True, linestyle='--', alpha=1)
plt.show()
添加参数vert=False
将箱形图横向展示:
scores.plot(kind='box', vert=False, figsize=(10, 5))
plt.grid(True, linestyle='--', alpha=1)
plt.show()
添加参数subplots=True
,可以形成多个子图:
scores.plot(kind='box', y=['Chinese', 'Maths', 'English', 'Physics'],
subplots=True, layout=(2, 2), figsize=(10, 8))
plt.show()
面积图,或称区域图,是一种随有序变量的变化,反映数值变化的统计图表,原理与折线图相似。而面积图的特点在于,折线与自变量坐标轴之间的区域,会由颜色或者纹理填充。
适用场景:
在连续自变量下,一组或多组数据的趋势变化以及相互之间的对比,同时也能够观察到数据总量的变化趋势。
例如,位移 = 速度 x 时间:即s=v*t
; 那么x 轴是时间 t,y 轴是每个时刻的速度 v,使用面积图,不仅可以观察速度随时间变化的趋势,还可以根据面积大小来感受位移距离的长度变化。
秋名山某段直线赛道上,AE86
与GC8
在60秒时间内的车速与时间的变化数据如下:
t = list(range(60))
v_AE86 = np.random.uniform(180, 210, len(t))
v_GC8 = np.random.uniform(190, 230, len(t))
v = pd.DataFrame({'t': t, 'AE86': v_AE86, 'GC8': v_GC8})
v.head(5)
t | AE86 | GC8 | |
---|---|---|---|
0 | 0 | 198.183060 | 215.830409 |
1 | 1 | 190.186453 | 195.316343 |
2 | 2 | 180.464073 | 210.641824 |
3 | 3 | 194.842767 | 219.794681 |
4 | 4 | 194.620050 | 204.215492 |
面积图默认情况下是堆叠的。
v.plot(kind='area', x='t', y=['AE86', 'GC8'], figsize=(10, 5))
plt.show()
要生成未堆积的图,可以传入参数stacked=False
:
v.plot(kind='area', x='t', y=['AE86', 'GC8'],
stacked=False, figsize=(10, 5), alpha=0.4)
plt.show()
从图形与x轴围成的面积看,很显然该60秒内,GC8
是略领先AE86
的。温馨提示,文明驾驶,切勿飙车!
同样,添加参数subplots=True
,可以形成多个子图,如下:
v.plot(kind='area', x='t', y=['AE86', 'GC8'],
subplots=True, figsize=(10, 5), rot=0)
plt.show()
本文是快速上手pandas的上篇,先介绍了为什么选用pandas,接下来介绍pandas的常用数据结构Series、DataFrame以及一系列的操作与运算,然后尝试了用pandas来读写文件,最后重点介绍了如何使用pandas快速便捷地可视化数据。
在快速上手pandas的下篇中,会涉及如何用pandas来进行数据清洗、数据合并、分组聚合、数据透视、文本处理等,敬请期待。