需要使用的数据
数据learn_pandas
Pandas基础
使用到的数据如下所示,可以将其保存为不同的文件以供练习
col1,col2,col3,col4,col5
2,a,1.4,apple,2020/1/1
3,b,3.4,banana,2020/1/2
6,c,2.5,orange,2020/1/5
5,d,3.2,lemon,2020/1/7
文件的读取和写入
读取csv、excel和txt文件
# 读取csv文件
csv_data = pd.read_csv("data/my_csv.csv")
print(csv_data)
# 读取excel文件
excel_data = pd.read_excel("data/my_excel.xlsx")
print(excel_data)
# 读取txt文件
table_data = pd.read_table("data/my_table.txt")
print(table_data)
这里有一些常用的公共参数, header=None
表示第一行不作为列名,index_col
表示把某一列或几
列作为索引, usecols
表示读取列的集合,默认读取所有的列, parse_dates
表示需要转化为时间的
列, nrows
表示读取的数据行数。上面这些参数在上述的三个函数里都可以使用。
第一行不做索引
csv_data_no_head = pd.read_csv("data/my_csv.csv", header=None)
print(csv_data_no_head)
选取单独列作为索引
csv_data_choice_col = pd.read_csv("data/my_csv.csv", index_col=['col1', 'col2'])
print(csv_data_choice_col)
指定读取的列
csv_data_choice_col_2 = pd.read_csv("data/my_csv.csv", usecols=['col1', 'col2'])
print(csv_data_choice_col_2)
处理时间
csv_data_time = pd.read_csv("data/my_csv.csv", parse_dates=['col5'])
print(csv_data_time)
选择读取的行数
csv_data_rows = pd.read_csv("data/my_csv.csv", nrows=3)
print(csv_data_rows)
数据的写入
一般在数据写入中,最常用的操作是把 index
设置为False
,特别当索引没有特殊意义的时候,这样
的行为能把索引在保存的时候去除。
df_csv.to_csv('data/my_csv_saved.csv', index=False)
df_excel.to_excel('data/my_excel_saved.xlsx', index=False)
pandas
中没有定义to_table
函数,但是 to_csv
可以保存为 txt 文件,并且允许自定义分隔符,
常用制表符 \t 分割:
df_txt.to_csv('data/my_txt_saved.txt', sep='\t', index=False)
基本数据结构
pandas
中具有两种基本的数据存储结构,存储一维values
的 Series
和存储二维values
的
DataFrame
,在这两种结构上定义了很多的属性和方法。
Series
Series
一般由四个部分组成,分别是序列的值 data
、索引index
、存储类型 dtype
、序列的名字
name
。其中,索引也可以指定它的名字,默认为空。
s = pd.Series(data=[100, 'a', {"name": "Jack"}],
index=pd.Index(["ID", 20, 'third'], name="my_idx"),
dtype='object',
name='my_name')
注意: object 代表了一种混合类型,正如上面的例子中存储了整数、字符串以及 Python 的字典数据结构。
对于这些属性,可以通过 .
的方式来获取:
# 获取值
print(s.values)
# 获取 索引
print(s.index)
# 获取 数据类型
print(s.dtype)
# 获取列名
print(s.name)
# 获取长度
print(s.shape)
DataFrame
DataFrame
在 Series
的基础上增加了列索引。
df = pd.DataFrame(data={'col_0': [1, 2, 3], 'col_1': list('abc'),
'col_2': [1.2, 1.3, 1.4]},
index=['row_%d' % i for i in range(3)])
在 DataFrame
中可以用[col_name]
与 [col_list]
来取出相应的列与由多个列组成的表,结果分
别为Series
和DataFrame
:
# 获取第一列,只获取列的时候就是一个Series
print(df['col_2'])
# 获取第一二两列
print(df[['col_1', 'col_2']])
与 Series
类似,在数据框中同样可以取出相应的属性:
# 获取值
print(df.values)
# 获取 索引
print(df.index)
# 获取长度
print(df.shape)
# 返回数据类型
print(df.dtypes)
# 获取列名
print(df.columns)
基本常用函数
为了进行举例说明,在接下来的部分和其余章节都将会使用一份learn_pandas.csv
的虚拟数据集,
它记录了四所学校学生的体测个人信息。
数据查看
df = pd.read_csv('data/learn_pandas.csv')
# 列名
print('列名\n', df.columns)
上述列名依次代表学校、年级、姓名、性别、身高、体重、是否为转系生、体测场次、测试时间、1000
米成绩,本章只需使用其中的前七列。
print("前七列\n", df[df.columns[:7]])
汇总函数,head
查看前几行,tail
查看后几行
# 前2行
print('前两行数据\n', df.head(2))
# 后三行
print('前两行数据\n', df.tail(3))
info
, describe
分别返回表的 信息概况 和表中 数值列对应的主要统计量 :
# 查看信息
print('查看信息\n', df.info())
# describe
print('describe\n', df.describe())
特征统计函数
在 Series
和 DataFrame
上定义了许多统计函数,最常见的是 sum
, mean
, median
, var
, std
,
max
, min
。例如,选出身高和体重列进行演示:
df_demo = df[['Height', 'Weight']]
print('平均值\n', df_demo.mean())
print('最大值\n', df_demo.max())
此外,需要介绍的是 quantile
, count
, idxmax
这三个函数,它们分别返回的是分位数(前百分之多少的数据)、非缺失值个数、最大值对应的索引:
print('分位数\n', df_demo.quantile(0.25))
print('非缺失值个数\n', df_demo.count())
print('最大值对应的索引\n', df_demo.idxmax())
上面这些所有的函数,由于操作后返回的是标量,所以又称为聚合函数,它们有一个公共参数 axis
,默认为0代表逐列聚合,如果设置为1则表示逐行聚合:
print('对行来进行聚合\n', df_demo.mean(axis=1).head())
唯一值函数
对序列使用 unique
和 nunique
可以分别得到其唯一值组成的列表和唯一值的个数:
跳转顶部
# 唯一值的列表
print("唯一值的列表\n", df['School'].unique())
# 唯一值得个数
print("唯一值的个数\n", df['School'].nunique())
value_counts
可以得到唯一值和其对应出现的频数:
print('唯一值和其出现的频数\n', df['School'].value_counts())
如果想要观察多个列组合的唯一值,可以使用 drop_duplicates
。其中的关键参数是keep
,默认值first
表示每个组合保留第一次出现的所在行,last
表示保留最后一次出现的所在行, False 表示把所有重复组合所在的行剔除。
df_demo = df[['Gender', 'Transfer', 'Name']]
print('两行重复输出第一次出现的值\n', df_demo.drop_duplicates(['Gender', 'Transfer']))
print('两行重复输出最后一次出现的值\n', df_demo.drop_duplicates(['Gender', 'Transfer'], keep='last'))
print('保留只出现过一次的数据\n', df_demo.drop_duplicates(['Name', 'Gender'], keep=False).head())
此外, duplicated
和 drop_duplicates
的功能类似,但前者返回了是否为唯一值的布尔列表,其keep
参数与后者一致。其返回的序列,把重复元素设为 True
,否则为False
。 drop_duplicates
等价于把 duplicated
为True
的对应行剔除。
print('展示是否为重复行\n', df_demo.duplicated(['Gender', 'Transfer']).head())
排序函数
排序共有两种方式,其一为值排序,其二为索引排序,对应的函数是 sort_values
和sort_index
。为了演示排序函数,下面先利用 set_index
方法把年级和姓名两列作为索引
df_demo = df[['Grade', 'Name', 'Height', 'Weight']].set_index(['Grade', 'Name'])
对身高进行排序,默认参数 ascending=True
为升序:
print('身高升序排序\n', df_demo.sort_values('Height').head())
print('身高降序排序\n', df_demo.sort_values('Height', ascending=False).head())
在排序中,经常遇到多列排序的问题,比如在体重相同的情况下,对身高进行排序,并且保持身高降序排列,体重升序排列:
print('身高降序,体重升序\n', df_demo.sort_values(['Weight', 'Height'], ascending=[True, False]).head())
索引排序的用法和值排序完全一致,只不过元素的值在索引中,此时需要指定索引层的名字或者层号,用参数 level 表示。另外,需要注意的是字符串的排列顺序由字母顺序决定。
print('索引排序\n', df_demo.sort_index(level=['Grade', 'Name'], ascending=[True, False]).head())
索引
表的列索引
列索引是最常见的索引形式,一般通过 [] 来实现。通过 [列名] 可以从 DataFrame
中取出相应的列,返回值为 Series
,例如从表中取出姓名一列:
df = pd.read_csv('data/learn_pandas.csv', usecols=['School', 'Grade', 'Name', 'Gender', 'Weight', 'Transfer'])
print('取出姓名一列\n', df['Name'].head())
如果要取出多个列,则可以通过 [列名组成的列表] ,其返回值为一个 DataFrame
,例如从表中取出性别和姓名两列:
print('取出多列\n', df[['Gender', 'Name']].head())
序列的行索引
如果取出单个索引的对应元素,则可以使用 [item]
,若 Series
只有单个值对应,则返回这个标量值,如果有多个值对应,则返回一个 Series
:
s = pd.Series([1, 2, 3, 4, 5, 6], index=['a', 'b', 'a', 'a', 'a', 'c'])
print('Series单个值对应\n', s['a'])
print('Series多个值对应\n', s[['c', 'b']])
loc索引器
前面讲到了对 DataFrame
的列进行选取,下面要讨论其行的选取。对于表而言,有两种索引器,一种是基于 元素 的loc
索引器,另一种是基于 位置 的 iloc 索引器。
loc
索引器的一般形式是 loc[*, *]
,其中第一个 * 代表行的选择,第二个 * 代表列的选择,如果省略第二个位置写作loc[*]
,这个 * 是指行的筛选。
df_demo = df.set_index('Name')
print('数据前五行\n', df_demo.head())
此时,直接取出相应的行或列,如果该元素在索引中重复则结果为 DataFrame
,否则为 Series
:
print('直接取出相应的列(数据重复)\n', df_demo.loc['Qiang Sun'])
print('直接取出相应的列(数据不重复)\n', df_demo.loc['Quan Zhao'])
也可以同时选择行和列:
print('同时选择行和列\n', df_demo.loc['Qiang Sun', 'School'])
此时,取出列表中所有元素值对应的行或列:
print('取出列表中所有元素值对应的行或列\n', df_demo.loc[['Qiang Sun', 'Quan Zhao'], ['School', 'Gender']])
iloc
的使用与 loc
完全类似,只不过是针对位置进行筛选,,函数的返回值必须是前面的四类合法对象中的一个,其输入同样也为 DataFrame
本身。
print('第二行第二列\n', df_demo.iloc[1, 1])
print('前两行前两列\n', df_demo.iloc[[0, 1], [0, 1]])
print('第二到四行,三到四列\n', df_demo.iloc[1: 4, 2:4])
分组
分组模式
分组的一般模式
分组操作在日常生活中使用极其广泛,例如:
- 依据 性别 分组,统计全国人口 寿命 的 平均值
- 依据 季节 分组,对每一个季节的 温度 进行 组内标准化
- 依据 班级 分组,筛选出组内 数学分数 的 平均值超过80分的班级
从上述的几个例子中不难看出,想要实现分组操作,必须明确三个要素:分组依据 、 数据来源 、 操作及其返回结果 。同时从充分性的角度来说,如果明确了这三方面,就能确定一个分组操作,从而分组代码的一般模式即:
df.groupby(分组依据)[数据来源].使用操作
现在返回到学生体测的数据集上,如果想要按照性别统计身高中位数,就可以如下写出:
df = pd.read_csv('data/learn_pandas.csv')
print('按照性别求身高中位数\n', df.groupby('Gender')['Height'].median())
聚合模式
了解一些直接定义在groupby对象的聚合函数,因为它的速度基本都会经过内部的优化,使用功能时应当优先考虑。根据返回标量值的原则,包括如下函数:
max/min/mean/median/count/all/any/idxmax/idxmin/mad/nunique/skew/quantile/sum/std/v
ar/sem/size/prod 。
gb = df.groupby('Gender')['Height']
print('最小值所在的行\n', gb.idxmin())
print('前百分之五的身高\n', gb.quantile(0.95))
gb = df.groupby('Gender')[['Height', 'Weight']]
print('按照列的迭代计算\n', gb.max())
连接
连接的基本概念
把两张相关的表按照某一个或某一组键连接起来是一种常见操作,例如学生期末考试各个科目的成绩表按照 姓名 和 班级 连接成总的成绩表,又例如对企业员工的各类信息表按照 员工ID号 进行连接汇总。由此可以看出,在关系型连接中, 键 是十分重要的,往往用 on
参数表示。
另一个重要的要素是连接的形式。在 pandas
中的关系型连接函数merge
和 join
中提供了how
参数来代表连接形式,分为左连接left
、右连接right
、内连接inner
、外连接outer
,它们的区别可以用如下示意图表示:
从图中可以看到,所谓左连接即以左表的键为准,如果右表中的键于左表存在,那么就添加到左表,否则则处理为缺失值,右连接类似处理。内连接只负责合并两边同时出现的键,而外连接则会在内连接的基础上包含只在左边出现以及只在右边出现的值,因此外连接又叫全连接。
上面这个简单的例子中,同一个表中的键没有出现重复的情况,那么如果出现重复的键应该如何处理?只需把握一个原则,即只要两边同时出现的值,就以笛卡尔积的方式加入,如果单边出现则根据连接形式进行处理。其中,关于笛卡尔积可用如下例子说明:设左表中键 张三 出现两次,右表中的 张三 也出现两次,那么逐个进行匹配,最后产生的表必然包含 2*2 个姓名为 张三 的行。下面是一个对应例子的示意图:
显然在不同的场合应该使用不同的连接形式。其中左连接和右连接是等价的,由于它们的结果中的键是被一侧的表确定的,因此常常用于有方向性地添加到目标表。内外连接两侧的表,经常是地位类似的(左右表位置的交换不引起结果的变化),想取出键的交集或者并集,具体的操作还需要根据业务的需求来判断。
值连接
在上面示意图中的例子中,两张表根据某一列的值来连接,事实上还可以通过几列值的组合进行连接,这种基于值的连接在 pandas
中可以由merge
函数实现,例如第一张图的左连接:
df1 = pd.DataFrame({'Name': ['San Zhang', 'Si Li'], 'Age': [20, 30]})
df2 = pd.DataFrame({'Name': ['Si Li', 'Wu Wang'], 'Gender': ['F', 'M']})
print('merge连接\n', df1.merge(df2, on='Name', how='left'))
在某些时候出现重复元素是麻烦的,例如两位同学来自不同的班级,但是姓名相同,这种时候就要指定on 参数为多个列使得正确连接:
df1 = pd.DataFrame({'Name': ['San Zhang', 'San Zhang'], 'Age': [20, 21], 'Class': ['one', 'two']})
df2 = pd.DataFrame({'Name': ['San Zhang', 'San Zhang'], 'Gender': ['F', 'M'], 'Class': ['two', 'one']})
print('左连接\n', df1.merge(df2, on=['Name', 'Class'], how='left'))
索引连接
所谓索引连接,就是把索引当作键,因此这和值连接本质上没有区别, pandas
中利用join
函数来处理索引连接,它的参数选择要少于 merge
,有on
和 how
。其中, on
参数指索引名,单层索引时省略参数表示按照当前索引连接。
df1 = pd.DataFrame({'Age': [20, 30]}, index=pd.Series(['San Zhang', 'Si Li'], name='Name'))
df2 = pd.DataFrame({'Gender': ['F', 'M']}, index=pd.Series(['Si Li', 'Wu Wang'], name='Name'))
print('索引连接\n', df1.join(df2, how='left'))
缺失数据
缺失值的统计和删除
缺失信息的统计
缺失数据可以使用 isna
或 isnull
(两个函数没有区别)来查看每个单元格是否缺失,结合 mean
可以计算出每列缺失值的比例:
df = pd.read_csv('data/learn_pandas.csv', usecols=['Grade', 'Name', 'Gender', 'Height', 'Weight', 'Transfer'])
print('查看是否缺失\n', df.isna().head())
print('缺失的比例\n', df.isna().mean())
如果想要查看某一列缺失或者非缺失的行,可以利用 Series 上的 isna 或者 notna 进行布尔索引。例如,查看身高缺失的行:
print('身高缺失的行\n', df[df.Height.isna()].head())
如果想要同时对几个列,检索出全部为缺失或者至少有一个缺失或者没有缺失的行,可以使用isna
,notna
和 any
, all
的组合。例如,对身高、体重和转系情况这3列分别进行这三种情况的检索:
sub_set = df[['Height', 'Weight', 'Transfer']]
print('全部缺失\n', df[sub_set.isna().all(1)])
print('至少缺失一个\n', df[sub_set.isna().any(1)].head())
print('没有缺失\n', df[sub_set.notna().all(1)].head())
缺失值的删除
数据处理中经常需要根据缺失值的大小、比例或其他特征来进行行样本或列特征的删除, pandas
中提供了 dropna
函数来进行操作。
dropna
的主要参数为轴方向axis
(默认为0,即删除行)、删除方式 how 、删除的非缺失值个数阈值 thresh
( 非缺失值 没有达到这个数量的相应维度会被删除)、备选的删除子集subset
,其中how 主要有any
和all
两种参数可以选择。
例如,删除身高体重至少有一个缺失的行:
# 删除身高体重至少有一个缺失的行
res = df.dropna(how='any', subset=['Height', 'Weight'])
print('删除身高体重至少有一个缺失的行\n', res)
例如,删除超过15个缺失值的列:
res = df.dropna(1, thresh=df.shape[0] - 15)
print('删除超过15个缺失值的列\n', res)
缺失值的填充
利用fillna
进行填充
在 fillna
中有三个参数是常用的: value
, method
, limit
。其中, value
为填充值,可以是标量,也可以是索引到元素的字典映射; method
为填充方法,有用前面的元素填充ffill
和用后面的元素填充bfill
两种类型,limit
参数表示连续缺失值的最大填充次数。
s = pd.Series([np.nan, 1, np.nan, np.nan, 2, np.nan], list('aaabcd'))
s.fillna(method='ffill') # 用前面的值向后填充
s.fillna(method='ffill', limit=1) # 连续出现的缺失,最多填充一次
s.fillna(s.mean()) # value为标量
s.fillna({'a': 100, 'd': 200}) # 通过索引映射填充的值