数据处理03:Python数据分析库Pandas
Pandas 是最强大的 Python 数据分析库,它在 NumPy 基础之上构建,功能完善、性能出色并且操作便捷。项目官网 http://pandas.pydata.org/
Pandas 已包含于 Anaconda 中,导入模块时请按惯例命名为 pd:
In [1]: import pandas as pd
In [2]: pd.__version__ # 查看版本号
Out[2]: '0.24.1'
Pandas 所提供的对象类型主要有“数据系列”(Series)和“数据网格”(DataFrame)——Series 像是一维数组而 DataFrame 像是二维数组,与数组的关键区别在于它们包含可自定义的“数据索引”(Index),类似于字典的键。DataFrame 中的列就是 Series 对象,每一列有各自的数据类型但共享相同的 Index。让我们先调用构造器创建一个 Series:
In [3]: pd.Series(["北京", "上海", "广州", "深圳"])
Out[3]:
0 北京
1 上海
2 广州
3 深圳
dtype: object
In [4]: Out[3].index # index属性指向索引
Out[4]: RangeIndex(start=0, stop=4, step=1)
In [5]: cityname = pd.Series(["北京", "上海", "广州", "深圳"], index=["bj", "sh", "gz", "sz"])
In [6]: cityname.index
Out[6]: Index(['bj', 'sh', 'gz', 'sz'], dtype='object')
新的 Series 使用城市名拼音缩写作为自定义索引,但要注意的是默认的序列索引仍然有效:前者称为显式索引而后者称为隐式索引,当以整数作为显式索引时这可能会引发混淆,因此你还可以用“定位器”属性 loc 和 iloc 来明确指定索引方式:
In [7]: cityname["sz"]
Out[7]: '深圳'
In [8]: cityname[-1]
Out[8]: '深圳'
In [9]: cityname.loc["bj"]
Out[9]: '北京'
In [10]: cityname.iloc[0]
Out[10]: '北京'
下面让我们再尝试创建 DataFrame,所用方式是向构造器传入一个由可索引对象组成的字典,所生成 DataFrame 的列数据和列标签就是字典的值和键,行索引是一个由所有列数据共用的 Index,列索引则是一个由所有列标签组成的 Index:
In [11]: citypop = {"bj": 1877.7, "sh": 2115, "gz": 1246.83, "sz": 1137.89}
In [12]: df = pd.DataFrame({"名称": cityname, "人口": citypop})
In [13]: df
Out[13]:
名称 人口
bj 北京 1877.70
gz 广州 1246.83
sh 上海 2115.00
sz 深圳 1137.89
In [14]: df.index # index属性指向行索引
Out[14]: Index(['bj', 'gz', 'sh', 'sz'], dtype='object')
In [15]: df.columns # columns属性指向列索引
Out[15]: Index(['名称', '人口'], dtype='object')
你可以使用 Series 与 DataFrame 的索引、属性或方法,以及模块的函数对数据执行各种操作,包括读取、更新和运算等等——注意方法和函数默认会返回新对象而非原地修改:
In [16]: df["名称"] # 以索引方式获取列
Out[16]:
bj 北京
gz 广州
sh 上海
sz 深圳
Name: 名称, dtype: object
In [17]: df.人口 # 以属性方式获取以标识符规则命名的列
Out[17]:
bj 1877.70
gz 1246.83
sh 2115.00
sz 1137.89
Name: 人口, dtype: float64
In [18]: df.iloc[2:] # 以序列索引方式获取行
Out[18]:
名称 人口
sh 上海 2115.00
sz 深圳 1137.89
In [19]: df["人口"].sum() # 列数据求和
Out[19]: 6377.42
In [20]: df.sort_values("人口", ascending=False) # 按人口列降序排列
Out[20]:
名称 人口
sh 上海 2115.00
bj 北京 1877.70
gz 广州 1246.83
sz 深圳 1137.89
In [21]: df["区号"] = ["010", "020", "021", "0755"] # 添加新列
...: df
Out[21]:
名称 人口 区号
bj 北京 1877.70 010
gz 广州 1246.83 020
sh 上海 2115.00 021
sz 深圳 1137.89 0755
In [22]: df.loc['tj'] = ['天津', 875.24, '022'] # 添加新行
...: df
Out[22]:
名称 人口 区号
bj 北京 1877.70 010
gz 广州 1246.83 020
sh 上海 2115.00 021
sz 深圳 1137.89 0755
tj 天津 875.24 022
In [23]: df[df.人口 >= 1000] # 按条件筛选
Out[23]:
名称 人口 区号
bj 北京 1877.70 010
gz 广州 1246.83 020
sh 上海 2115.00 021
sz 深圳 1137.89 0755
In [24]: df.人口 >= 1000 # 筛选的原理是用布尔值系列来索引
Out[24]:
bj True
gz True
sh True
sz True
tj False
Name: 人口, dtype: bool
In [25]: df2 = pd.DataFrame([['重庆', 851.8, '023'], ['南京', 617.82, '025']], columns=["名称", "人口", "区号"], index=["cq", "nj"])
...: pd.concat([df, df2]) # 拼接两个 DataFrame
Out[25]:
名称 人口 区号
bj 北京 1877.70 010
gz 广州 1246.83 020
sh 上海 2115.00 021
sz 深圳 1137.89 0755
tj 天津 875.24 022
cq 重庆 851.80 023
nj 南京 617.82 025
接下来的例子是对中国历史上皇帝们的寿命进行统计分析,这次使用现成数据来生成 DataFrame。Pandas 支持读取多种类型的资源,例如以逗号作为分隔符的文本格式(CSV):
# 短网址对应的原文件
# https://gitee.com/freesand/pyStudy/raw/master/data/emperor.csv
df = pd.read_csv("http://t.cn/EMl0NtB")
对于大尺寸 DataFrame,推荐先用 shape 和 dtypes 属性查看形状和列数据类型,也可用 head() 方法预览前 5 行内容,DataFrame 在 Jupyter Notebook 中会以表格形式输出:
print("数据网格形状:", df.shape)
print("各列数据类型:")
print(df.dtypes)
df.head()
03_pandas_emperor.png
对于已生成的 DataFrame,还可以进行一些调整操作(修改列标签、去除多余内容等)再开始数据分析,例如列出寿命达到 80 岁的皇帝:
df.columns = ["序号", "名号", "寿命", "生卒", "朝代"]
df[df.寿命 >= 80]
03_pandas_filter.png
筛选出明清两朝的皇帝:
mingqing = df[df.朝代.isin(["明", "清"])]
mingqing.head()
03_pandas_mingqing.png
比较明清两朝的皇帝寿命——聚合输出分组总计数、最低值、最高值、平均值、中位数:
compare = mingqing.groupby("朝代").寿命.agg(["count", "min", "max", "mean", "median"])
compare
03_pandas_mingqingcomp.png
还可以根据全体皇帝的寿命数据绘制直方图来显示值的分布:
import matplotlib.pyplot as plt
plt.style.use("seaborn")
plt.hist(df.寿命, range=(0, 100)) # 直方图,范围0至100(默认为最小值到最大值)
03_pandas_pyplot.png
Pandas 的功能非常丰富,想更深入地了解请查看官方文档 http://pandas.pydata.org/pandas-docs/stable/
——编程原来是这样……