极速入门Pandas数据分析
数据载入
DataFrame
Pandas 有 Series 和 DataFrame 两种数据结构,对应一维数组和二维表,Series 可以理解成是一个只有一列的 DataFrame。
pd.DataFrame()
可以直接创建 DataFrame 表,参数可以是字典或二维数组。使用字典创建表时,字典中的每个键值对代表一列数据,使用二维数组创建表时,一个内层数组代表一行数据。
创建 DataFrame 表时,还有一些常用的参数
- index 指定每一行的 index,等于给每一行起个名字,类似 HBase 的行键,默认是从零开始的数字。
- columns 指定列名,默认是从零开始的数字。
例如现在把下面这个数据载入成 DataFrame
Name | Age | Id |
---|---|---|
Ahay | 22 | 1001 |
Bronya | 24 | 1002 |
ZJN | 19 | 1004 |
# use dict
pd.DataFrame({
"Name": ['Ahay', 'Bronya', 'ZJN'],
"Age": [22, 24, 19],
"Id": ['1001', '1002', '1003']
})
# or use list
pd.DataFrame([
["Ahay", 22, "1001"],
["Bronya", 24, "1002"],
["ZJN", 19, "1004"]
],columns=["Name","Age","Id"])
加载文件
读文件的函数都有个通用参数 encoding 用来指定文件编码
read_csv
从 csv 中读取文件,估计是最常用的吧,参数如下:
- sep 指定字段的分隔符
- names 指定表头,默认会用文件第一行做表头
- header 指定文件第几行是表头,这行号也是从零开始数的,表头上面的内容会被忽略
DataFrame 操作
单元计算
DataFrame 与一个基本数据类型做计算,等于 DataFrame 中每个单元格对这个数据做计算
例如一个数据:
df = pd.DataFrame({
"age":[22,23],
"id":[1101,1102]
})
进行乘方运算df**2
age id
0 484 1212201
1 529 1214404
或进行比较df > 1000
age id
0 False True
1 False True
reindex
DataFrame 用 index 来表示每一行,默认 index 是从零开始数的行号
使用 reindex() 方法可以重定义 index,或在生成 DataFrame 时指定 index
在重定义 index 时,会移动已经存在的行的位置,不存在的行会填充为NaN,新 index 没有的行会被删除
df = pd.DataFrame({
'value':[1,2,3]
},index=['a','c',2])
df.reindex(['a','b','c'])
# value
# a 1.0 # 这一行没动
# b NaN # 这一行原来不存在,所以填充为NaN
# c 2.0 # 这一行原来存在,现在被移动到了这里
如果想让某个表的 index 和 columns 快速变为另一个表的样子,可以使用 reindex_like() 方法:
df = pd.DataFrame({
'value': [1, 2, 3]
}, index=['a', 'c', 2])
newDf = pd.DataFrame({
'value': [6, 7, 8]
})
newDf = newDf.reindex_like(df)
print(newDf)
# value
# a NaN # 原来不存在
# c NaN # 原来不存在
# 2 8.0 # 原来存在(因为默认 index 是 0、1、2),值不变
默认情况下 reindex_like() 方法会同时重定义 index 和 columns
rename
rename() 可以给 DataFrame 的 index 或 columns 重命名,有两个参数分别是 columns 和 index,它们的值是字典,key 是原来的名字,value 是新名字。
# 把两个列名翻译成中文
df = df.rename(columns={
"name":"姓名",
"age":"年龄"
})
字典中不存在的 index 或 columns 不会被更改。
要区别于 reindex,reindex 会对表中数据产生影响,其实reindex 就是根据 index 对表重新整理。
而 rename 只是对表头改个名
DataFrame 数据选取
DataFrame 切片
列切片
DataFrame 的切片中第一个数据如果是一个字符串或一个包含字符串的列表,则为取列。
df["Id"]
df[["Name","Age"]]
只取一列且懒得写切片时,可以直接点取列:
df.Name
# 等同于
df['Name']
数据选取不会产生新数据,因此可以直接对原表进行修改
例如,把所有人名字改成 abc:
df['Name'] = 'abc'
loc 行切片
loc 的直接用法和列切片差不多,不过选择结果是行,所以切片里面不写 column 而是写 index
老数据:
df = pd.DataFrame({
"name":["A","B","C","D"],
"age":[22,23,26,23],
"id":[1101,1102,1103,1104]
})
选择前两个数据df.loc[:1]
由于 loc 是按照 index 进行选择,index 不一定是数字,所以 loc 切片包含开头也包含结尾
name age id
0 A 22 1101
1 B 23 1102
还有各种写法,这里就写个大概:
df.loc[[1,3]] # 选择第二行和第四行
df.loc[[1,3],['name']]# 选择第二行和第四行的name列,在赋值操作时这样用
df.loc[[1,3]]['name'] # 选择第二行和第四行的name列,结果只能查不能修改
iloc 行切片
loc 按照 index 选择行,但 index 并不是行号。
df = pd.DataFrame(
[1,2,3,4,5],
index=['a','b',1,2,3]
)
print(df.loc[2])
# 结果会显示数字4,因为 index 被修改了
所以还有个 iloc 按照行号选择行,例如输出前三行print(df.iloc[:3])
,这个情况下用 loc 就可能出问题,因为 index 可能被修改过,导致 3 不一定指第四行。
因为是按照行号选择,就类似 python 数组的索引,所以 iloc 切片选择包含开头但不包含结尾
at、iat 单元选择
at 和 iat 的用法和 loc、iloc 一样,不过 at、iat 们的返回结果只是一个数据,不是一组,所以 at、iat 的切片中必须仅有两个元素来分别表示行和列,由此确定唯一的元素
df.at[4,'name'] # at 的写法更严格,只能取一个元素
# 等同于
df.loc[4,'name']
DataFrame 中的 Series
不论是上面的 loc 还是列切片,都有个奇怪的特性:切片中的第一个元素可以是一个列表也可以是单独的一个值。
例如df['name']
和df[['name']]
都是正确且效果相同的
而且df[['name','age']]
不能写成df['name','age']
这是因为他们的返回值不同
只取一列或一行的情况下,会得到一个 Series 列表,而取多行时得到的结果还是一个 DataFrame 表
type(df['name']) # Series
type(df[['name']]) # DataFrame
type(df['name','age']) # 原地报错
type(df.loc[1]) # Series
type(df.loc[[1]]) # DataFrame
type(df.loc[1,'name']) # str 这样其实就是先取行再取列,所以结果是数据本身的类型
type(df.loc[[0,2,4],['name','age']]) # DataFrame 和上一行类似,先取0、2、4行,再取它们的列
type(df.loc[1,['name','age']]) # Series 只取了一行,所以是个 Series
可以看到通过控制切片中第一个元素是数组还是数据,就可以控制返回结果是 Series 还是 DataFrame 了
DataFrame 筛选
在使用 loc 或直接对 DataFrame 切片时,如果切片中的第一个数据是一个包含布尔值的列表,则会按照这些布尔值选取对应的行。
例如还是这个数据集
df = pd.DataFrame({
"name":["A","B","C","D"],
"age":[22,23,26,23],
"id":[1101,1102,1103,1104]
})
选择第二行和第四行df.loc[[False,True,False,True]]
name age id
1 B 23 1102
3 D 23 1104
Series 也可以当做列表来用,因此利用 DataFrame 的列切片选取某一列后进行条件判断,可以快速生成一个与源数据有关的布尔值列表,再配合这个条件切片就可以实现类似 SQL 中 WHERE 的效果:
选择 age 大于25的数据df.loc[df['age'] > 25]
name age id
2 C 26 1103
这个操作也可以直接对 DataFrame 进行,既不通过 loc 切片
df.loc[df['age'] > 25] # 等同于 df[df['age'] > 25]
对筛选部分赋值
筛选后的结果可以进行多次切片,但只有筛选后不切片的结果才能赋值
# 把大于40岁的人员年龄设置为40
# 一次切片完成操作
df.loc[df['age'] > 40, 'age'] = 40
# 这样不行,因为在条件切片后面又对列切片了一次
df.loc[df['age'] > 40]['age'] = 40
逻辑运算
如果需要 and、or、not 这些逻辑操作,需要使用位运算符 &、|、~ 来表示。
如果是用来做筛选条件处理,这种操作只能对 Series 数据结构使用。
位运算符的优先级高于比较运算符,所以要记得对位运算符两边的表达式加括号。
(df['age'] > 20) & (df['age'] < 40) # 这两对括号必加
字符串处理
pandas 的字符串处理只针对 Series 数据结构有效,所以不能对多列或多行进行操作。
Series 结构有一个 str 属性提供多种字符串处理函数,例如:
- contains() 是否能部分匹配正则
- match() 是否能完全匹配正则
- split() 分割字符串成列表
- len() 长度
- replace() 替换
- extract() 使用正则提取数据,正则中使用括号包裹被提取的内容
- get_dummies() 用分隔符分隔字符串,并用每一部分的值生成哑变量
基本上普通字符串有的这里都有
如果是简单的判断字符串相等的话不需要 str 直接 == 就行了
现在有一个测试表,有 id、姓名、性别、年龄 字段,包含三十条纪录,内容可以看文档最后的【测试用例】部分。
结合之前的内容,可以实现一些基础的查询功能:
# 读取csv文件,这句话以后就省略了
df = pd.read_csv(r"D:\笔记\Bigdata\pandas.csv", encoding='utf-8')
# 获取成年男性信息 [SQL] WHERE sex == ’男‘ and age >= 18
df[(df['sex'] == '男') & (df['age'] >= 18)]
# 获取名字中包含 雪 字的人员信息
df[(df['name'].str.contains('雪'))]
日期处理
Series 结构有一个用于日期处理的 dt 属性,用法和 str 一样,有以下属性:
- second、hour、day等 快速获取时分秒日期等
- tz_localize() 转换时区
- strftime("%d %h这样的表达式") 格式化成字符串
此外,如果表中原有的日期列没有识别成日期数据类型,可以用 pandas 包下的 to_datetime 方法转换
df = pd.DataFrame({
'name': "ABCDE",
'time': ['20201212', '20201214', '20201211', '20201212', '20201217']
})
# 转换一下类型
df['time'] = pd.to_datetime(df['time'])
# 输出 12 日那天的数据
print(df[df['time'].dt.day == 12])
name time
0 ABCDE 2020-12-12
3 ABCDE 2020-12-12
query 快速查询
query 可以使用类Python原生语法快速查询数据,例如:
df.query('A < B')
# 查询结果等价于
df[df["A"] < df["B"]]
# 逻辑运算还用 not and or,判断等于依旧是双等于号
df.query('A > 40 and sex == "F"')
# 查询结果等价于
df[(df["A"] > 40) & (df["sex"] == "F")]
df.query("not (age > 12)")
# 查询结果等价于
df[~(df["age"] >12)]
这样的后果就是不能进行赋值操作了
删除
删除行
drop() 方法会返回删除某行数据后的 DataFrame,参数是被删除的 index
# 删除年龄大于 40 的
df.drop(df[df['age'] > 40].index)
如果希望使用原地算法删除,可以指定上 inplace = True 参数
删除列
列删除可以直接使用 del df[‘name’] 的形式,也可以给 drop() 指定 axis
del df['name'] # 删除 name 列
删除重复
drop_duplicates() 方法删除重复的内容,保留第一次出现的纪录,参数可以填列名或不写代表全部列
# 一共有几种名字
len(df['name'].drop_duplicates())
# 25
分组聚类
groupby 分组
对 DataFrame 使用 groupby() 进行分组,参数填上分组字段就行了,多个字段用列表表示。
分组后的结果不是 DataFrame 而是 DataFrameGroupBy,想获取数据需要调用一系列聚类函数:
- mean() 平均值
- count() 计数
- sum() 求和
- 等等等
# 不同性别的年龄平均值
df.groupby('sex')['age'].mean()
# 名字加性别的重复数,忽略不重复的名字
a = df.groupby(['sex', 'name'])['name'].count()
a[a > 1]
# sex name
# 女 珍 3
# 芹 2
# 男 亮 2
# 平 2
如果不希望分组字段变成 index 可以指定 as_index=False
value_counts 快速计数
如果只是对某个字段分组计数,可以直接对 Series 使用 value_counts() 方法来省略 groupby()
# 不同性别的人数
df['sex'].value_counts()
排序
DataFrame 按字段排序
sort_values() 方法按照某些字段的值进行排序,返回排序后的 DataFrame
第一个参数是排序字段,可以是一个列名字符串或一个包含列名字符串的列表
ascending 参数指定是否升序排序,可以是一个布尔值,或是一个布尔值列表来表示每个排序字段的顺序
# 按年龄排序,从小到大,相同性别的放在一起
df = df.sort_values(['sex', 'age'], ascending=[False, True])
# 结果就这样
# id name sex age
# 9 517 豪 男 8
# 13 956 瑜 男 12
# 20 512 亮 男 15
# 27 796 鑫 女 8
# 7 479 童 女 15
# 21 887 莹 女 17
DataFrame 按 index 排序
sort_index() 按照 index 排序
# 给 df 换一个 index
df = df.reindex(range(29, -1, -1))
# 再按照 index 排序
df = df.sort_index()
Series 排序
Series 也支持 sort_values 和 sort_index ,其中 sort_values 就不用指定列名了
数据操作 API 函数
应用函数
pandas 可以对数据应用自定义函数,有以下几种方式
- pipe(func) 表级操作,func 接受的参数是一个 DataFrame
- apply(func,axis=0) 对 DataFrame 是行级操作,func 接受的参数是代表一列或一行的 Series ,对 groupby 结果操作则是组级操作,既 func 的参数是由每个组构成的 DataFrame
- applymap(func) 元素级操作,对 Series 操作还可以用 map(),func 接受的参数就是单元格中的数值
在函数内部可以对元数据进行修改,除非指定了 raw=True,内部函数的返回值会被应用到一个位置对应的新 DataFrame
def func(x):
return 1 if x == "男" else 0
# 把性别转换成1、0
df['sex'] = df['sex'].apply(func)
聚合应用函数
有两个针对 groupby 结构的应用函数:
- agg(func) 聚合操作使用
- transform(func) 分类转换操作使用
# 将年龄小于50的年龄更改为同性别的年龄总和
def transform(x):
m = x.sum()
x[x < 50] = m
return x
df['age'] = df.groupby('sex')['age'].transform(transform)
# 去掉最高最低,求每个性别的平均年龄
def agg(x: pd.Series):
x = x.drop(x.idxmin())
x = x.drop(x.idxmax())
return x.mean()
print(df.groupby('sex')['age'].agg(agg))
替换
有 map() 和 replace() 方法可以替换单元中的内容。
- map 只能对 Series 使用,它的参数是一个字典,key 是单元格中现有的值,value 是新的值,不在 key 中的值会被设为 NaN
- replace 用法和一般的 replace 一样,一个参数表示原有值,第二个参数表示新值,其中这两个参数可以写成一个列表来一次替换多种数据
例如:
# 把 男 替换成 1,女 替换成 0
df['sex'] = df['sex'].map({'男': 1, '女': 0})
# 另一种写法
df['sex'] = df['sex'].replace(['男', '女'], [1, 0])
# 特征工程:工资这一列里的低中高替换成 0,1,2
df = df.replace({'工资': {'低': 0, '中': 1, '高': 2}})
map 的参数还可以是一个方法,在应用函数那一节有介绍
平移
shift() 函数可以让数据位置发生变化
df = pd.DataFrame([1, 2, 3], columns=['value'])
# 整体向后移动一下
df.shift(1)
# value
# 0 NaN
# 1 1.0
# 2 2.0
利用 shift 可以实现连续重复数据剔除:
se = pd.Series([1, 2, 2, 2, 3, 3, 4, 2, 4, 4, 5, 8, 4])
se.drop(se[se.shift(1) == se].index)
# 1, 2, 3, 4, 2, 4, 5, 8, 4
统计函数
这些统计函数可以对 Series 结构做运算,大部分也能对 DataFrame 做运算,在 DataFrame 计算时就是把每一列当做 Series 做计算。
sum() 求和
median() 中位数
mean() 平均值
max() 最大值
**min() **最小值
idxmax() 最大值的 index
idxmin() 最小值的 index
cumsum() 累加,每个单元格的值等于这个单元格的值,再加上这个单元格上面全部单元格的值的和
mad() 平均绝对离差:
1
n
∑
i
=
1
n
∣
x
i
−
x
‾
∣
\frac{1}{n} \sum^{n}_{i=1} |x_i - \overline{x}|
n1i=1∑n∣xi−x∣
手算的话就是:(data - data.mean()).abs().mean()
std() 标准差,百度了一下发现标准差分总体标准差和样本标准差两种,std() 默认算的是样本标准差:
∑
i
=
1
n
(
x
i
−
x
‾
)
2
n
−
1
\sqrt{\frac{ \sum^{n}_{i=1} (x_i - \overline{x})^2} { n-1 }}
n−1∑i=1n(xi−x)2
pandas 手算就是:
upper = ((data - data.mean()) ** 2).sum() # 分子
lower = (len(data) - 1) # 分母
print((upper / lower) ** 0.5) # 相除,开方
var() 方差,也分总体方差和样本方差,var() 默认也是样本方差:
∑
(
x
−
x
‾
)
2
n
−
1
\frac{\sum (x - \overline{x}) ^ 2}{n-1}
n−1∑(x−x)2
pandas 手算:((data - data.mean())**2).sum() / (len(data) - 1)
diff() 一阶差分,就是每一个单元格相对于上一个单元格增加的量,第一个单元格值为NaN
pct_change() 百分数变化,和 diff() 类似,表示每一个单元格相对上一个单元格变化的百分比,第一个单元格值为NaN
corr() 计算列与列之间的相关性、相关性系数,如果是对 Series 计算,则需要指定其他 Series 作为比较对象,对 DataFrame 计算则会列出每一列之间的结果。这个东西的结果表示两列数据的相关程度,越接近相同的数据计算结果越接近 1,符号相反但绝对值相同的数据计算结果会是-1,毫无关联的两组数据结果会接近0
first() 获取第一行,配合 groupby 可以获得每组的第一个,在 groupby 前面 sort_values 可以获得每组的最值
最值
使用 max() 或 min() 取最大最小值,使用 nlargest() 或 nsmallest() 取指定数量的最值,这几个函数可以对 groupby结果、DataFrame、Series 使用
# 不同性别中最年长的人
df.groupby('sex').max()
# id name age
# sex
# 女 954 香 73
# 男 956 豪 65
# id 前 10 的人员信息
df.loc[df['id'].nsmallest(10).index]
# 等同于
df.nsmallest(10, columns='id')
# id name sex age
# 19 356 芹 女 58
# 6 369 珍 女 73
# 16 424 磊 男 31
还有 idxmax() 和 idxmin() 两个函数可以获取最大值和最小值所在的 index。
分段
pd.cut() 函数可以对某一个 Series 进行分段操作,返回一个 index + 区间范围 的 Series :
cut = pd.cut(df['age'], [0, 18, 35, 60, 120])
# 0 (18, 35]
# 1 (35, 60]
# 2 (18, 35]
# 3 (0, 18]
# ......
# 不同年龄段的人数
df.groupby(cut).count()['id']
# (0, 18] 8
# (18, 35] 6
# (35, 60] 13
# (60, 120] 3
小坑,cut 函数不在 Series 类里面,而是在 Pandas 包下面
窗口
rolling() 来对元数据进行滑动窗口的数值计算
d = pd.Series([1, 10, 100, 1000, 10000])
print(d.rolling(3).sum())
# 0 nan
# 1 nan
# 2 111.000000
# 3 1110.000000
# 4 11100.000000
还可以指定一个 center=True 参数让窗口从中间向两侧延伸
透视表 pivot_table
DataFrame 使用 pivot_table() 函数生成透视表,参数如下:
- index 指定哪些列作为 index
- values 指定那些列会被作为结果
- aggfunc 对数据的聚合函数,sum、mean、count 等
- columns 把某个字段中的每一个值挪到 columns 上,
- margins 是否在表格末行末列添加汇总
- fill_value 将空值填充为这个参数
# 统计不同性别人数
df.pivot_table(index=['sex'], aggfunc='count', values='name')
# name
# sex
# 女 18
# 男 12
奇怪的东西
一些目前还无法理解或不明所以的东西
- DataFrame 支持迭代,迭代的结果是表头,和 df.columns 好像没啥区别
准备整理的东西
duplicated() 查看是否重复,以及keep参数
keep{‘first’, ‘last’, False}, default ‘first’
Determines which duplicates (if any) to mark.
first
: Mark duplicates asTrue
except for the first occurrence.last
: Mark duplicates asTrue
except for the last occurrence.- False : Mark all duplicates as
True
.
any() 列中出现true就整列是true,相反的是all(),配合T转置可以实现某行是否存在空值
astype(‘int’) 转换类型
fillna() 空值填充,以及各种填充方式
join() 根据index连表
merge() 根据字段连表
read_sql()
pd.concat() 横向拼接表
np.where()
In [13]: df['logic'] = np.where(df['AAA'] > 5, 'high', 'low')
In [14]: df
Out[14]:
AAA BBB CCC logic
0 4 10 100 low
1 5 20 50 low
2 6 30 -30 high
3 7 40 -50 high
# 找到 AAA 每一组中 BBB 最小的哪一行
df.loc[df.groupby("AAA")["BBB"].idxmin()]
set_index()
stack() 堆叠展示数据,把每行展示成这样
# 元数据
id name age
0 1 aaa 22
1 2 bbb 24
stack()之后就是
0 id 1
name aaa
age 22
1 id 2
name bbb
age 24
最左边是 index,然后是每个列名和值
reset_index()
get_group() 获取 goroupby 结果中的某个组,例如class为[1,2,3,4]的DataFrame df
df.groupby('class').get_group(2) # 获得由 class 为 2 这一组数据构成的 DataFrame
df.expanding() 这是个啥
pd.get_dummies()
merge、concat、duplicated实现差集并集交集计算
寻找异常值,对数值列进行Min、max、mean、std、var检查
可视化时对比性强的数据画成并联柱状图或者多条折线图最好
unique() 对 Series 不重复提取
pd.crosstab() 交叉表
日期处理的时候提取 周 ,还有 dt.isocalendar()
股票分析代码解读
unstack()
copy()
df.update() 参数 overwrite=False 空值替换
dropna() thresh 参数
df.replace() 的 regex=True参数
df.sample(frac=1)
测试用例
人员信息表 (ID、姓名、性别、年龄)
共三十行
|id|name|sex|age|
|776|亮|男|21|
|500|平|男|42|
|893|梅|女|33|
|834|昌|男|16|
|478|朋|女|58|
|494|彬|男|38|
|369|珍|女|73|
|479|童|女|15|
|836|伟|女|36|
|517|豪|男|8|
|781|然|男|16|
|470|慧|女|30|
|780|保|男|54|
|956|瑜|男|12|
|800|珍|女|57|
|505|娥|女|45|
|424|磊|男|31|
|954|香|女|53|
|637|英|女|38|
|356|芹|女|58|
|512|亮|男|15|
|887|莹|女|17|
|456|荣|女|57|
|909|平|男|65|
|743|雪|女|29|
|465|家|女|35|
|897|芹|女|54|
|796|鑫|女|8|
|677|珍|女|61|
|474|海|男|45|