Python 数据挖掘 | 第3章 使用 Pandas 数据分析
前言
使用 Pandas 数据分析
1. Pandas 概述
- Pandas 是 Python 语言的一个扩展程序库,用于数据分析;
- 特点:
- 便捷的数据处理能力;
- 封装了Matplotlib、Numpy的画图和计算;
- 读取文件方便;
1.1 核心数据结构
- 三大数据结构:DataFrame、Pannel、Series;
1.1.1 Series
- Series 可以理解成带索引的一维数组(只有行索引);
- API:
pd.Series(np.arange(10))
:指定内容,默认索引;pd.Series([1,8,5], index=["a", "b", "c"])
:指定索引;pd.Series({'red': 100, 'blue': 200, 'green': 300})
:通过字典构造数据;
- 属性:
- index:索引;
- values:值;
1.1.2 DataFrame
- DataFrame 可以理解成即带行索引,又带列索引的二维数组;
- 可以把 DataFrame 理解成 Series 的容器;
- 结构:
- 既有行索引,又有列索引的二维数组;
- 行索引,表明不同行,横向索引,叫 index;
- 列索引,表明不同列,纵向索引,叫 columns;
- 常用属性:
- shape;
- index 行索引列表;
- columns 列索引列表;
- values 直接获取其中array的值;
- T 行列转置;
- 常用方法:
- head() 开头几行;
- tail() 最后几行;
- API:
pd.DataFrame(ndarray, index, columns)
:构造一个 DataFrame;pd.date_range(start="20200101", periods=5, freq="B")
:添加日期作为索引;- start:开始日期;
- end:结束时间;
- periods:指定生成时间序列的数量;
- freq:生成频率,默认‘D’,可以是’H’、‘D’、‘M’、‘5H’、‘10D’;
- 索引的设置:
- 修改行列索引:只能整体修改,不能单独改;
stock_ = ["股票_{}".format(i) for i in range(10)]
;data.index = stock_
- 重设索引:用的不多;
data.reset_index(drop=False)
:drop:False 表示将索引加入到数据中。drop:True 表示直接删掉索引;
- 设置新索引:
data.set_index(keys, drop=True)
:- keys:列索引名称或列索引名称列表,当索引名称为列表时,返回 MultiIndex 对象;
- drop:False 表示将索引加入到数据中。True 表示删掉索引;
- 修改行列索引:只能整体修改,不能单独改;
- MultiIndex:多级或分层索引对象;
- index 属性:
- names:levels 的名称;
- levels:每个 level 的元组值;
- index 属性:
1.1.3 Pannel
- Pannel 可以理解成一个三维数据的结构;
- Pandas 从 0.20.0 开始弃用 Pannel,推荐使用 DateFrame 上的 MultiIndex 表示 3D 数据;
- API:
p = pd.Panel(np.arange(24).reshape(4,3,2), items=list('ABCD'), major_axis=pd.date_range('20130101', periods=3), minor_axis=['first', 'second'])
:构造一个三维 Pannel,可以理解成为 DataFrame 容器,直接打印不能展示数据,要通过维度访问;- items:维度。可以通过 p[“A”] 访问其中一个维度;
- major_axis:时间。可以通过
p.major_xs("2013-01-01")
访问该时间维度; - minor_axis:次序。可以通过
p.minor_xs("first")
访问该次序维度;
code1 DataFrame、Panel 和 Series 的构造与访问代码示例
# 创建一个符合正态分布的10个股票5天的涨跌幅数据
stock_change = np.random.normal(0, 1, (10, 5))
# 1.构造 DataFrame
## 1.1 通过 ndarray 数组生成
stock = ["股票{}".format(i) for i in range(10)] # 行索引
date = pd.date_range(start="20180101", periods=5, freq="B") # 列索引
data = pd.DataFrame(stock_change, index=stock, columns=date)
## 1.2 通过字典生成
df = pd.DataFrame({'month': [1, 4, 7, 10],
'year': [2012, 2014, 2013, 2014],
'sale':[55, 40, 84, 31]})
# 1.3修改行列索引
stock_ = ["股票_{}".format(i) for i in range(10)]
data.index = stock_
# 1.4 重设索引
# data.reset_index(drop=False)
# 1.5 设置新索引
df.set_index("month", drop=True) # 以月份设置新的索引
new_df = df.set_index(["year", "month"]) # 设置多个索引,以年和月份
print(new_df.index) # 返回 MultiIndex 可以表示三维数据
# 1.6 MultiIndex
print(new_df.index.names) # levels 的名称
print(new_df.index.levels) # 每个 level 的元组值
# 2.构造 Panel
p = pd.Panel(np.arange(24).reshape(4,3,2),
items=list('ABCD'),
major_axis=pd.date_range('20130101', periods=3),
minor_axis=['first', 'second'])
# 2.1 访问维度
print(p["A"])
p.major_xs("2013-01-01")
p.minor_xs("first")
# 3.构造 Series
pd.Series(np.arange(10)) # 指定内容,默认索引;
pd.Series([1, 8, 5], index=["a", "b", "c"]) # 指定索引;
pd.Series({'red': 100, 'blue': 200, 'green': 300}) # 通过字典构造数据;
2. 基本数据操作
2.1 索引操作
- 直接索引(先列后行)
a = data["open"]["2018-02-26"]
- 按名字索引
b = data.loc["2018-02-26", "open"]
- 按数字索引
c = data.iloc[1, 0]
- 组合索引
d = data.loc[data.index[0:4] , ['open', 'close', 'high', 'low']]
:获取第1天到第4天的 [‘open’, ‘close’, ‘high’, ‘low’];e = data.iloc[0:4 , data.columns.get_indexer(['open', 'close', 'high', 'low'])]
2.2 赋值操作
- 修改多个值:
data.open = 100
:将open列所有值改成100;data["open"] = 100
:将open列所有值改成100;
- 修改单个值:
data.iloc[1, 0] = 222
:修改某个值;
2.3 排序
- 对内容进行排序:
data.sort_values(by=["xxx", "yyy"], ascending=False)
:ascending=False降序,ascending=True升序;
- 对索引进行排序:
data.sort_index()
;
3. DataFrame 运算
3.1 算数运算
- 算数运算:
data["yyy"].add(x)
:yyy 下所有元素值加 x;data1.sub(data2)
:data1 一一对应减去 data2;
3.2 逻辑运算
- 逻辑运算:
data[data["yyy"] > x]
:筛选 yyy 下所有值大于 x 的元素;data[(data["xxx"] > n) & (data["yyy"] < m)]
:筛选 xxx 下所有值大于 n,并且 yyy 小于 m的元素;data.query("xxx > n & yyy < m")
:使用逻辑运算函数;data[data["xxx"].isin([n, m])]
:判断 xxx 是否为 n, m;
3.3 统计运算
- 统计运算:(默认列计算)
data.describe()
:获取常用统计值;data.max(axis=0)
:获取最大值;data.idxmax(axis=0)
:获取最大值所在位置;
3.4 累计统计函数
- 累计统计函数:
data.cumsum()
:计算前1/2/3/…/n个数的和;data.cummax()
:计算前1/2/3/…/n个数的最大值;data.cummin()
:计算前1/2/3/…/n个数的最小值;data.cumprod()
:计算前1/2/3/…/n个数的积;
3.5 自定义运算
- 自定义运算(默认列计算)
data.apply(lambda x: x.max() - x.min())
;
code2 索引操作、值操作、排序与运算代码示例
# 1.读取数据
data = pd.read_csv("../../resources/p00_data_mining/stock_day.csv")
# 删除一些列
data = data.drop(["ma5","ma10","ma20","v_ma5","v_ma10","v_ma20"], axis=1)
# 2.索引操作
## 2.1 直接索引(先列后行)
a = data["open"]["2018-02-26"]
## 2.2 按名字索引
b = data.loc["2018-02-26", "open"]
## 2.3 按数字索引
c = data.iloc[1, 0]
## 2.4 组合索引
d = data.loc[data.index[0:4] , ['open', 'close', 'high', 'low']] # 获取第1天到第4天的 ['open', 'close', 'high', 'low']
e = data.iloc[0:4 , data.columns.get_indexer(['open', 'close', 'high', 'low'])]
# 3.值操作
data.open = 100 # 将open列所有值改成100
data["open"] = 100 # 将open列所有值改成100
data.iloc[1, 0] = 222 # 修改某个值
# 4.排序
data.sort_values(by=["high", "p_change"], ascending=False).head() # 对内容进行排序。ascending=False降序,ascending=True升序
data.sort_index()
# 5.DataFrame 运算
## 5.1 算数运算
data["open"].add(3).head() # 加法
data.sub(100).head() # 加法
data["close"].sub(data["open"]).head() # 减法
## 5.2 逻辑运算
data[data["p_change"] > 2].head() # 例如筛选p_change > 2的日期数据
data[(data["p_change"] > 2) & (data["low"] > 15)].head() # 完成一个多个逻辑判断, 筛选p_change > 2并且low > 15
data.query("p_change > 2 & low > 15").head() # 逻辑运算函数
f = data[data["turnover"].isin([4.19, 2.39])] # 判断'turnover'是否为4.19, 2.39
## 5.3 统计运算
data.describe() # 获取常用统计值
data.max(axis=0) # 获取最大值
data.idxmax(axis=0) # 获取最大值所在位置
## 5.4 累计统计函数
data["p_change"].sort_index().cumsum().plot() # plot()画图
## 5.5 自定义运算
data.apply(lambda x: x.max() - x.min())
4. Pandas 画图
- API:
pandas.DataFrame.plot(x=None, y=None, kind=‘line’)
- x: 标签或索引,默认 None;
- y: 标签、索引或者列表的标签、索引,默认 None;
- 允许绘制一列与另一列的对比图
- kind: str;
- ‘line’:折线图(默认);
- ''bar":柱状图;
- “barh”:水平条形图;
- “hist”: 直方图;
- “pie”:饼图;
- “scatter”:散点图;
pandas.Series.plot(kind="line")
;
code3 简单画图代码示例
# 1.读取数据
data = pd.read_csv("../../resources/p00_data_mining/stock_day.csv")
# 删除一些列
data = data.drop(["ma5","ma10","ma20","v_ma5","v_ma10","v_ma20"], axis=1)
# 2.画图
#更简易用matplotlib
data.plot(x="volume", y="turnover", kind="scatter")
data.plot(x="high", y="low", kind="scatter")
data['volume'].plot()
5. 文件读取与存储
5.1 CSV 文件的读取与存储
5.1.1 读取 read_csv()
pandas.read_csv(filepath_or_buffer, sep=',', delimiter=None, usecols=[], names=[])
:- filepath_or_buffer:文件路径;
- sep:分隔符;
- usecols:指定读取的列名,列表形式;
- names:如果列没有列名,用names传入;
code4 CSV文件的读取与存储代码示例
# 1.读取数据
data0 = pd.read_csv("../../resources/p00_data_mining/stock_day.csv", usecols=["high", "low", "open", "close"]).head() # 读哪些列
data = pd.read_csv("../../resources/p00_data_mining/stock_day2.csv", names=["open", "high", "close", "low", "volume", "price_change", "p_change", "ma5", "ma10", "ma20", "v_ma5", "v_ma10", "v_ma20", "turnover"]) # 如果列没有列名,用names传入
# 2.写入数据
data[:10].to_csv("../../resources/p00_data_mining/test1.csv", columns=["open"]) # 保存open列数据前10行
data[:10].to_csv("../../resources/p00_data_mining/test2.csv", columns=["open"], index=False, mode="a", header=False) # 保存opend列数据,index=False不要行索引,mode="a"追加模式|mode="w"重写,header=False不要列索引
5.1.2 存储 to_csv()
DataFrame.to_csv(filepath_or_buffer, sep=',', columns=None, header=True, index=True, index_label=None, mode='w', encoding=None)
:- filepath_or_buffer:文件路径;
- sep:分隔符;
- columns:列,列表形式;
- model:读写模式:w-重写,a-追加;
- index:是否写进 行索引;
- header:是否写进 列索引;
Series.to_csv(...)
5.2 hdf5 文件的读取与存储
5.2.1 读取 read_hdf()
pandas.read_hdf(path_or_buf, key=None, **kwargs)
- path_or_buffer: 文件路径;
- key: 读取的键;
- model: 打开文件的模式;
- reurn: The Selected object
5.2.2 存储 to_hdf()
DataFrame.to_hdf(path_or_buf, key, **kwargs))
code5 hdf5文件的读取与存储代码示例
# 1.读取数据
day_close = pd.read_hdf("../../resources/p00_data_mining/day_close.h5")
# 2.写入数据
day_close.to_hdf("../../resources/p00_data_mining/test3.h5", key="close")
# 测试
day_close_test = pd.read_hdf("../../resources/p00_data_mining/test3.h5")
5.3 json 文件的读取与存储
5.3.1 读取 read_json()
pandas.read_json(path_or_buf=None,orient=None,typ=“frame”,lines=False)
;- 将JSON格式转换成默认的 Pandas DataFrame 格式;
- orient: string,以怎样的格式展示读取进来的 json 文件;
- ‘split’: 字典 like {index -> [index], columns -> [columns], data -> [values]};
- ‘records’: 列表 like [{column -> value}, …, {column -> value}];
- ‘index’: 字典 like {index -> {column -> value}};
- ‘columns’: 字典 like {column -> {index -> value}}, 默认该格式;
- ‘values’: just the values array;
- lines: boolean, default False;
- 按照每行读取json对象
- type: default ‘frame’,指定转换成的对象类型series或者dataframe
5.3.2 存储 to_json()
DataFrame.read_json(path_or_buf=None,orient=None,lines=True)
;
code6 json文件的读取与存储代码示例
sa = pd.read_json("../../resources/p00_data_mining/Sarcasm_Headlines_Dataset.json", orient="records", lines=True)
##主要是path,orient是一种确定索引与数值的对应,以本例来看,列索引就是‘key’,values就是key对应的值
sa.to_json("../../resources/p00_data_mining/test4.json", orient="records", lines=True)
6. 缺失值处理
6.1 缺失值处理的思路
- 删除含有缺失值的样本;
- 替换/插补缺失值;
6.2 如何处理 nan
- 判断缺失值:
pd.isnull(data)
:判断数据是否为 nan。一般会结合 any() 函数使用;np.any(pd.isnull(movie))
:返回True,说明数据中存在缺失值;pd.isnull(movie).any()
:返回True,说明数据中存在缺失值;
pd.notnull(data)
:判断数据是否为 nan。一般会结合 all() 函数使用;np.all(pd.notnull(movie))
:返回False,说明数据中存在缺失值;pd.notnull(movie).all()
:返回False,说明数据中存在缺失值;
- 处理缺失值 nan:
data.dropna(axis='rows', inplace=)
:删除缺失值;- axis:默认删除行;
- inplace:True 时会修改原数据。False 时不会替换修改原数据,生成新对象(默认);
data.fillna(value, inplace=)
:替换缺失值;- value:替换成的值;
- inplace:True 时会修改原数据。False 时不会替换修改原数据,生成新对象(默认);
data.replace(to_replace="?", value=np.nan)
:如果缺失值不是 np.nan 时,需要先把缺失值替换成 np.nan,再进行缺失值处理;- to_replace:需要处理的缺失值;
- value:替换成的值;
code7 nan缺失值处理代码示例
# 1.读取数据
movie = pd.read_csv("../../resources/p00_data_mining/IMDB-Movie-Data.csv")
# 2.判断是否存在缺失值
np.any(pd.isnull(movie)) # 返回True,说明数据中存在缺失值
np.all(pd.notnull(movie)) # 返回False,说明数据中存在缺失值
pd.isnull(movie).any()
pd.notnull(movie).all()
# 3.缺失值处理
# 方法1:删除含有缺失值的样本
data1 = movie.dropna()
# 方法2:替换
# 含有缺失值的字段:Revenue (Millions) 和 Metascore
movie["Revenue (Millions)"].fillna(movie["Revenue (Millions)"].mean(), inplace=True)
movie["Metascore"].fillna(movie["Metascore"].mean(), inplace=True)
code8 其他缺失值处理代码示例
# 1.读取数据
path = "https://archive.ics.uci.edu/ml/machine-learning-databases/breast-cancer-wisconsin/breast-cancer-wisconsin.data"
name = ["Sample code number", "Clump Thickness", "Uniformity of Cell Size", "Uniformity of Cell Shape", "Marginal Adhesion", "Single Epithelial Cell Size", "Bare Nuclei", "Bland Chromatin", "Normal Nucleoli", "Mitoses", "Class"]
data = pd.read_csv(path, names=name)
# 2.将缺失值替换成 np.nan
# 1)替换
data_new = data.replace(to_replace="?", value=np.nan)
# 2)删除缺失值
data_new.dropna(inplace=True)
data_new.isnull().any() # 全部返回False说明不存在缺失值了
7. 数据离散化
7.1 概述
- 定义:连续属性的离散化就是将连续属性的值域上,将值域划分为若干个离散的区间,最后用不同的符号或整数 值代表落在每个子区间的属性值;
- 目的:连续属性离散化的目的是为了简化数据结构,数据离散化技术可以用来减少给定连续属性值的个数。离散化方法经常作为数据挖掘的工具;
7.2 实现离散化
- 对数据进行分组:
pd.qcut(data, bins)
:自动分组;- data:需要分组的数据,Series;
- bins:组数;
pd.cut(data, bins)
:自定义分组;data.value_counts()
:统计分组次数。对数据分组一般会与 vaule_counts 搭配使用,统计每组个数;- 对分好的数据求哑变量(one-hot 编码):
pandas.get_dummies(data, prefix=None)
;- data:数据,Series、DataFrame;
- prefix:分组名字;
code9 数据离散化代码示例
# 1)准备数据
data = pd.Series([165,174,160,180,159,163,192,184], index=['No1:165', 'No2:174','No3:160', 'No4:180', 'No5:159', 'No6:163', 'No7:192', 'No8:184'])
# 2)分组
# 自动分组
sr1 = pd.qcut(data, 3)
# 自定义分组
bins = [150, 165, 180, 195]
sr2 = pd.cut(data, bins)
# 查看分组情况
sr1.value_counts()
sr2.value_counts()
# 3)转换成one-hot编码
pd.get_dummies(sr1, prefix="height1")
pd.get_dummies(sr2, prefix="height2")
8. 合并
8.1 按方向合并
pd.concat([data1, data2], axis=1)
:axis:0为列索引,列值2;1为行索引,行值2。字段不一致时会产生缺失值 nan;
code10 按方向合并代码示例
# 1.读取数据
stock = pd.read_csv("../../resources/p00_data_mining/stock_day.csv")
p_change = stock["p_change"]
# 2.自定义分组
bins = [-100, -7, -5, -3, 0, 3, 5, 7, 100]
sr = pd.cut(p_change, bins)
# 3.离散化 one-hot
stock_change = pd.get_dummies(sr, prefix="rise")
# 4.处理好的one-hot编码与原数据合并
pd.concat([stock, stock_change], axis=1) # 列索引合并,行值*2
pd.concat([stock, stock_change], axis=0).head()
8.2 按索引合并
pd.merge(left, right, how="inner", on=[])
:- left:DataFrame 数据;
- right:DataFrame 数据;
- on:索引;
- how:如何合并。inner:内连接(共有字段做全连接);outer:外连接(全连接);left:左连接(左表保留做全连接);right:右连接;
- 以 key1,key2 作为索引链接为例:
- 内连接:
- 左连接:
- 右连接:
- 外连接:
code11 按索引合并代码示例
left = pd.DataFrame({'key1': ['K0', 'K0', 'K1', 'K2'],
'key2': ['K0', 'K1', 'K0', 'K1'],
'A': ['A0', 'A1', 'A2', 'A3'],
'B': ['B0', 'B1', 'B2', 'B3']})
right = pd.DataFrame({'key1': ['K0', 'K1', 'K1', 'K2'],
'key2': ['K0', 'K0', 'K0', 'K0'],
'C': ['C0', 'C1', 'C2', 'C3'],
'D': ['D0', 'D1', 'D2', 'D3']})
# 内连接
pd.merge(left, right, how="inner", on=["key1", "key2"])
# 左连接
pd.merge(left, left, how="left", on=["key1", "key2"])
# 外连接
pd.merge(left, right, how="outer", on=["key1", "key2"])
9. 交叉表与透视表
- 交叉表和透视表:寻找两个列之间的关系,相当于以某个标签进行分组,一个统计分组个数,一个统计分组比例;
- 交叉表:计算一列数据对于另外一列数据的分组个数;
- 透视表:计算一列数据对于另外一列数据的分组比例;
9.1 交叉表
pd.crosstab(data["value1"], data["value2"])
:生成交叉表;
9.2 透视表
data.pivot_table(["value1"], index=["value2"])
:生成透视表;
code12 交叉表与透视表代码示例
# 探究星期数据以及涨跌幅是好是坏数据
# 1.读取数据
stock = pd.read_csv("../../resources/p00_data_mining/stock_day.csv")
# 2.准备数据
# 星期数据:pandas日期类型
date = pd.to_datetime(stock.index)
stock["week"] = date.weekday
# 涨跌幅数据
stock["pona"] = np.where(stock["p_change"] > 0, 1, 0)
# 3.交叉表
data = pd.crosstab(stock["week"], stock["pona"])
# 3.1 做除法后画图
data.div(data.sum(axis=1), axis=0).plot(kind="bar", stacked=True)
# 4.透视表操作
stock.pivot_table(["pona"], index=["week"])
10. 分组与聚合
- 分组
- 分组后不能直接看到结果,还要聚合;
data.groupby(by="yyy", as_index=False)
:DataFrame 方式进行分组。按 yyy 进行分组;data["xxx"].groupby(data["yyy"])
:Series 方式进行分组。效果同上;- 分组+聚合:
data.groupby(by="yyy", as_index=False)["xxx"].max()
:按 yyy 进行分组,然后输出每组 xxx 中的最大值;data["xxx"].groupby(data["yyy"])
:效果同上,先根据标签拿到 Series,再进行分组;
code13 分组与聚合代码示例
# 1.准备数据
col =pd.DataFrame({'color': ['white','red','green','red','green'], 'object': ['pen','pencil','pencil','ashtray','pen'],'price1':[5.56,4.20,1.30,0.56,2.75],'price2':[4.75,4.12,1.60,0.75,3.15]})
# 2.分组+聚合:进行分组,对颜色分组,price1进行聚合
# 2.1 用dataframe的方法进行分组
col.groupby(by="color")["price1"].max()
# 2.2 Series 方式进行分组
col["price1"].groupby(col["color"]).max()