机器学习入门 01 —— 机器学习概述
机器学习入门 02 —— 环境搭建(Jupyter Notebook 及扩展库的安装与使用)
机器学习入门 03 —— Matplotlib使用
机器学习入门 04 —— Numpy使用
机器学习入门 05 —— Pandas使用
机器学习入门 06 —— Seaborn使用
学习目标:
了解Numpy与Pandas的不同
说明Pandas的Series与Dataframe两种结构的区别
了解Pandas的MultiIndex结构
应用Pandas实现基本数据操作
应用Pandas实现数据的合并
应用crosstab和pivot_table实现交叉表与透视表
应用groupby和聚合函数实现数据的分组与聚合
了解Pandas的plot画图功能
应用Pandas实现数据的读取和存储
有了numpy和matplotlib,为什么还要使用Pandas?
读取文件方便
封装了Matplotlib和Numpy的画图与计算
Pandas可以增强图表可读性:左边是原生的数据显示,右边是用Pandas的显示。
Pandas中有三种数据结构:
Series是类似于一维数组的数据结构,它能存储任何类型的数据。Series由索引和数据构成。
import pandas as pd
pd.Series(data=None, index=None, dtype=None)
data:传入的数据,可以是ndarry、列表、字典、元祖等。
index:索引,要求是唯一值,且数量和data一一对应。如果没有传入索引,则默认从0开始。如果data是字典,索引就是key。
dtype:数据类型。
DataFrame类似于二维数组,它就像一个表格,所以既有行索引又有列索引。
import pandas as pd
pd.DataFrame(data=None, index=None, columns=None)
index:行标签。如果没有传入,则默认创建0-N作为索引。
columns:列标签。如果没有传入,则默认创建0-N作为索引。
reset_index(drop=False)
设置新的下标索引(0-N)。drop如果为True表示要删除原来的索引值。
set_index(keys, drop=True)
设置keys为新索引,drop默认为True,删除原索引。
上面把年份和月份都设置为索引,这其实就是一个MultiIndex(多重索引)
MultiIndex是三维的数据结构,多重索引,其实就是在Series、DataFrame对象上拥有两个或两个以上的索引的结构。
我们打印下刚刚的年月份的索引:
创建MultiIndex
为了更好的理解这些基本操作,这里的例子会先读取一个真实的文件数据。(关于文件操作后面会介绍,这里看看就行)
# 读取文件(csv文件其实就是Excel)
data = pd.read_csv('./stock_day.csv')
# 删除一些列,让数据更简单,再进行后面操作
data = data.drop(["ma5","ma10","ma20","v_ma5","v_ma10","v_ma20"], axis=1)
data.head(3)
要特别注意,Pandas直接操作索引是先列后行,和列表、Numpy的先行后列不同!!
# 直接使用列行操作(注意,是先列后行且不能切片。这和列表、Numpy的先行后列不同)
data['open']['2018-02-27'] # 获取到 23.53
data['open'] # 获取到open列所有值
# 下面的用法是错误的
data['2018-02-27']['open'] # 不能先行后列
data[:1][:2] # 不能使用切片
# 使用loc就是先行后列,还可以使用切片(注意,不能用[][],只能[: , :])
data.loc['2018-02-27':'2018-02-23','open':'close']
# 使用iloc也是先行后列,但用的是索引下标而不是索引值
data.iloc[:4, :3]
# loc和iloc还可以用于创造新列,例如我们想要知道每行的open是否大于22,用1和0表示
# data.loc[条件, 新列或旧列] = 赋值,如果存在旧列就是对旧列赋值。
data.loc[data["open"] > 22, "open大于22"] = 1
data.loc[data["open"] <= 22, "open大于22"] = 0
# loc和iloc混合使用(原理上还是一样的)
# 第一个参数实质上还是传递索引值
data.loc[data.index[0:4], ['open', 'close', 'high', 'low']]
# 第二个参数实质上还是传递索引下标
data.iloc[0:4, data.columns.get_indexer(['open', 'close', 'high', 'low'])]
# 把'close'列全部赋值为1
data.close = 1 # 或者 data['close'] = 1
# 把'close'列 '2018-02-27'行 赋值为2
data.loc['2018-02-27', 'close'] = 2
# 把第4列大于23的赋值为0
data[data.iloc[:,3]>22]=0
data.head(3)
Series和DataFrame都可以进行排序,根据索引排序,也可以根据值排序。
DataFrame排序
sort_values(by=keys,ascending=True)
:根据值排序。by是排序参考的键(可以多个),ascending为True是升序(默认)。sort_index(ascending=True)
:根据索引排序。升序。# 根据值排序,返回一个新对象,不会改变原来对象; 根据'open'列,升序;
data.sort_values('open', ascending=True).head(3)
# 根据'open'列和'high'列,降序;(如果open相同才比较high)
data.sort_values(['open','high'], ascending=False).head(3)
# 根据索引 从小到大
data.sort_index().head(3)
Series排序
由于Series只有一列,所以不需要传入参数,要么根据值排序要么根据索引排序。
sort_values(ascending=True)
:根据值排序。ascending=True升序(默认)。sort_index(ascending=True)
:根据索引排序。# 将DataFrame中某一列取出来就是Series
# 利用Series的值排序 升序
data['open'].sort_values(ascending=True).head(3)
# 利用Series的索引 降序
data['open'].sort_index(ascending=False).head(3)
# 加法 'open'列每个元素+10
data['open'].add(10) # 等价于 data['open']+10
# 减法 'open'
data['open'].sub(10)
# 乘法
data['open'].mul(10)
# 除法
data['open'].div(10)
# 筛选 'open' > 23的数据
a = data['open'] > 23 # 每一行 会返回True或False
data[a] # 获取 'open' > 23的数据
# 多个逻辑运算符
data[(data['open'] > 23) & (data['open'] < 24) ]
# 使用逻辑运算符函数 query(表达式)
data.query('open > 23 & open < 24')
# 判断 ‘open'列 有某个元素
b = data['open'].isin([23.53, 23.85]) # 会对每行的'open'进行判断,返回True或False
data[b] # 获取'open'是23.53 或者 23.85的数据
下面的函数可以对Series和DataFrame操作。对于DataFrame的统计函数,都有参数axis
,axis=0
表示对列进行统计(默认),axis=1
表示对行进行统计。
统计函数 | 描述 |
---|---|
sum() | 求和 |
mean() | 平均值 |
median() | 中位数(是从小到大的中间一位数字) |
min() | 最小值 |
max() | 最大值 |
mode() | 众数(出现次数最多的) |
abs() | 绝对值 |
prod() | 标准差 |
std() | 标准差 |
var() | 方差 |
idxmax() | 最大值的索引 |
idxmin() | 最小值的索引 |
cumsum() | 对某一列累计求和 |
cummax() | 对某一列累计求最大值 |
cummin() | 对某一列累计求最小值 |
cumprod() | 对某一列累计求积 |
可以自己定义函数进行运算
'''
apply(function, axis=0)
function:自定义的函数;axis:0 对列,1对行。
'''
def function(x):
return x.max() - x.min()
# 举例 下面定义一个队列 求最大值与最小值的差值
# 等价于 data[['open', 'close']].apply(function)
data[['open', 'close']].apply(lambda x: x.max() - x.min())
Pandas中封装了Matplotlib的画图,所以用法大致相同。在Pandas中使用画图函数plot
,同时,需要先导入Matplotlib。
无论是DataFrame还是Series都是使用plot()
画图。
DataFrame.plot(kind='line')
:
下面举个简单例子:
import matplotlib.pyplot as pltimport pandas as pd# 读取文件data = pd.read_csv('./Pandas测试数据.csv')# 删除一些列,让数据更简单,再进行后面操作data = data.drop(["ma5","ma10","ma20","v_ma5","v_ma10","v_ma20"], axis=1)# 排序data = data.sort_index()data = data['p_change'].cumsum()data.plot()plt.show() # 之所以要导入matplotlib,是因为show()后才能显示
我们的数据大部分都存储在文件中,而Pandas支持多种文件操作,例如CSV、HDF5、JSON、SQL、XLS等。
最常用的是HDF5和CSV。优先选择HDF5:
- HDF5在存储的时候支持压缩,使用的方式是blosc,这个是速度最快的也是pandas默认支持的
- 使用压缩可以提磁盘利用率,节省空间
- HDF5还是跨平台的,可以轻松迁移到hadoop 上面
下面这张表是常用API
'''
读取CSV
pandas.read_csv(filepath_or_buffer, sep=',', usecols)
filepath_or_buffer:文件路径
sep:分隔符,默认用,
usecols:指定读取的列名(用列表类型)
'''
data = pd.read_csv('./Pandas测试数据.csv', usecols=['open', 'close'])
'''
写入csv
DataFrame.to_csv(path_or_buf=None, sep=',', columns=None, header=True, index=True, mode='w', encoding=None)
path_of_buf:文件路径
sep:分隔符
columns:要写入的列索引
header:是否写入【列索引】
index:是否写入【行索引】
mode: 'w'重写,'a'追加
encoding:编码格式
'''
# 通常我们写入文件时,会把行索引也写入进去,行索引会变成一列数据,所以我们可以用index=False不写入行索引
data[:10].to_csv('test.csv', columns=['open'],index=False)
'''
写入HDF5
DataFrame.to_hdf(path_or_buf, key)
path_or_buffer:文件路径
key:读取的键(HDF5的读取和存储都要指定一个key)
'''
data.to_hdf('hdf5_data.hdf', 'HDF5_DATA')
'''
读取HDF5(读取需要导入tables模块)
pandas.read_hdf(path_or_buf, key=None)
path_or_buffer:文件路径
key:读取的键(HDF5的读取和存储都要指定一个key)
'''
data = pd.read_hdf('hdf5_data.hdf','HDF5_DATA')
JSON存储形式有几种:
{index -> [index], columns -> [columns], data -> [values]}
columns:values
,通常用这种。index:{columns:values}...
columns:{index:values}
'''
JSON的存储
DataFrame.to_json(path_or_buf=None, orient=None, lines=False)
path_or_buf:文件路径
orient:存储JSON的形式 【'split'、'records','index','colummns','values'】
lines:一个对象存储一行(建议设置为True)
'''
data.to_json('jsondata.json', orient='records')
'''
pandas.read_json(path_or_buf=None, orient=None, typ='frame', lines=False)
typ : default ‘frame’, 指定转换成的对象类型series或者dataframe
'''
pd.read_json('jsondata.json',orient='records')
我们获取到的数据不一定都是完整的,可能某个数据有缺失,所以我们需要先对错误数据进行处理。
NaN
或者?
之类的,需要我们先查看数据进行判断。NaN
NaN
的函数:pd.isnull(DataFrame)
、pd.notnull(DataFrame)
dropna(axis='rows)'
fillna(value, inplace=True)
,其中value替换的值,inplace=True会修改原表。NaN
而是?
之类的
?
替换为NaN
(),再按上述方式处理。下面进行举例说明:
非NaN
的缺失值,例如?
:这是下面的链接
连续数据离散化是为了简化数据结构,数据离散化技术可以用来减少给定连续属性值的个数。离散化方法经常作为数据挖掘的工具。
就是在连续数据的值域上,将值域划分为若干个离散的区间,最后用不同的符号或整数值代表落在每个子区间中的属性值。下面举个栗子:
在很多学习任务中,特征并不总是连续值,而有可能是分类值。
离散特征的编码分为两种情况:
1、离散特征的取值之间没有大小的意义,比如color:[red,blue],那么就使用one-hot编码
2、离散特征的取值有大小的意义,比如size:[X,XL,XXL],那么就使用数值的映射{X:1,XL:2,XXL:3}
One-Hot编码又称为独热编码(或哑变量 dummy variable),我们把数据离散化后的每个类别区间转为布尔列,这些列中只有一个可以为True(1)。例如下面这种表格,把【Human、Penguin、Octopus、Alien】分为了四个布尔列。
pd.qcut(data, q)
:返回Series。对数据进行分组(区间是自动分配的),q是分组个数。通常会再搭配value_counts()
用来统计每组里数据个数。pd.cut(data, bins)
:返回Series。也是对数据分组,但区间由自己指定。bins是分组区间。pd.get_dummines(data, prefix=None)
:data是Series或者DataFrame,prefix是分组的名称。下面举例:
# 读取CSV数据(某日股票数据)
data = pd.read_csv('stock_day.csv')
p_change = data['p_change']
qcut = pd.qcut(p_change, 10) # 将p_change分10组
qcut.value_counts() # 查看每组的数据个数
bins = [-100, -7, -5, -3, 0, 3, 5, 7, 100]
cut = pd.cut(p_change, bins) # 将p_change按bins分组
cut.value_counts() # 查看每组的数据个数
# 查看热编码
pd.get_dummies(cut).head()
有时候我们会想讲多张表格合并在一起,就需要用到pd.concat()
或者pd.merge()
pd.concat([data1, data2], axis=1)
,第一个参数是表格数据的列表,第二个参数为1则按行索引合并,为0则按列索引合并。# 将刚刚的One-Hot编码与原数据合并
data = pd.read_csv('stock_day.csv')
bins = [-100, -7, -5, -3, 0, 3, 5, 7, 100]
cut = pd.cut(p_change, bins)
dummies = pd.get_dummies(cut, prefix='rise')
pd.concat([data, dummies], axis=1) # 按行索引
pd.merge(left, right, how='inner', on=None)
left = pd.DataFrame({'A': ['A0', 'A1', 'A2', 'A3'],
'B': ['B0', 'B1', 'B2', 'B3'],
'key1': ['K0', 'K0', 'K1', 'K2'],
'key2': ['K0', 'K1', 'K0', 'K1']})
right = pd.DataFrame({'C': ['C0', 'C1', 'C2', 'C3'],
'D': ['D0', 'D1', 'D2', 'D3'],
'key1': ['K0', 'K1', 'K1', 'K2'],
'key2': ['K0', 'K0', 'K0', 'K0']})
内连接:
# 默认用内连接 inner,使用两个表共同键作为连接点
pd.merge(left, right) # 可以不指定on=['key1', 'key2']
左连接:
# 左连接
pd.merge(left, right, how='left', on=['key1', 'key2'])
右连接:
# 右连接
pd.merge(left, right, how='right', on=['key1', 'key2'])
外连接:
# 外连接
pd.merge(left, right, how='outer', on=['key1', 'key2'])
透视表:是将原有的DataFrame的某些列来分别作为行索引和列索引,然后对指定的列应用聚集函数
pd.pivot_table(data, # 制作透视表的数据
values=None, # 值
index=None, # 行索引
columns=None, # 列属性
aggfunc='mean', # 使用的聚集函数,默认是均值,其他还有 np.sum,size,max,min等
fill_value=None, # 缺失值填充
margins=False, # 是否显示总计
dropna=True, # 缺失值处理
margins_name='All', # 汇总的字段显示为All
observed=False,
)
import numpy as np
import pandas as pd
# 构造数据
data = pd.DataFrame([[5000, 34, "男", "是", "是"],
[8000, 33, "男", "否", "是"],
[7500, 27, "女", "是", "否"],
[6300, 24, "男", "否", "否"],
[5500, 23, "女", "是", "是"],
[5200, 43, "男", "否", "是"],
[4400, 26, "男", "是", "否"],
[3500, 25, "女", "否", "否"]],
columns=["收入", "年龄", "性别", "是否已婚", "是否吸烟"])
# 注意 使用的统计都是 平均值,可以使用aggfunc=['size','mean']来指定多个聚集函数
pd.pivot_table(data, index=['性别', '是否吸烟'], columns=['是否已婚'], margins=True, margins_name="汇总")
交叉表:是一种特殊的透视表,用于统计两列数据之间的关系。
pandas.crosstab(index, # 行索引,必须是数组结构数据,或者Series,或者是二者的列表形式
columns, # 列字段;数据要求同上
values=None, # 待透视的数据
rownames=None, # 行列名字
colnames=None,
aggfunc=None, # 透视的函数
margins=False, # 汇总及名称设置
margins_name='All',
dropna=True, # 舍弃缺失值
normalize=False # 数据归一化;可以是布尔值、all、index、columns、或者{0,1}
)
import numpy as np
import pandas as pd
# 构造数据 订单
data = pd.DataFrame([[3000, "鸡蛋"],
[2020, "牙刷"],
[3000, "可乐"],
[3000, "鸡蛋"],
[5501, "雪糕"],
[4003, "雪糕"],
[5501, "牙刷"],
[5450, "鸡蛋"],
[5501, "牙刷"]],
columns=["用户号", "商品"])
# 统计两列的数据 例如 用户号3000 购买了1次可乐 2次鸡蛋
pd.crosstab(data["用户号"],data["商品"])
分组与聚合通常是分析数据的一种方式,通常与一些统计函数一起使用,查看数据的分组情况。其实刚才的交叉表与透视表也有分组的功能,所以算是分组的一种形式,只不过他们主要是计算次数或者计算比例。下面这张图就非常形象。
聚合的内置函数:sum(), mean(), max(), min(), count(), size(), describe()
下面用代码演示:
星巴克案例:
# 读取数据 (数据来自Kaggle)
starbucks = pd.read_csv("directory.csv")
# 按国家分组,并求出每个国家星巴克零售店的数量
count = starbucks.groupby(['Country']).count()
# 绘图显示
count['Brand'].pl ot(kind='bar', figsize=(20, 8))
plt.show()
# 对国家和省份进行分组
starbucks.groupby(['Country', 'State/Province']).count().head()
现在对2006年至2016年1000部流行电影进行分析:
先导包并读取电影数据。
import pandas as pd
import numpy as np
import matplotlib.pyplot as plt
movie = pd.read_csv('IMDB-Movie-Data.csv')
movie.head(3)
# 平均分
movie['Rating'].mean()
# 导演人数
movie['Director'].unique().shape[0] # 方法一 unique()是去除重复元素
np.unique(movie['Director']).shape[0] # 方法二
如果使用Pandas直接绘制直方图,会出现坐标无法对齐的问题,所以还是要使用Matplotlib。
# 如果使用Pandas绘制直方图,会出现坐标无法对齐的问题
movie['Rating'].plot(kind='hist', figsize=(14, 6))
# 1. 创建画布
plt.figure(figsize=(20,8),dpi=80)
# 2. 绘制图像
plt.hist(movie["Rating"].values,bins=20) # 分为20组(即间隔)
# 2.1 创建刻度表(否则还是会有无法对齐的问题)
max_rating = movie['Rating'].max()
min_rating = movie['Rating'].min()
x_tick = np.linspace(min_rating, max_rating, num=21) # 均分为21个数字,中间就有20个间隔
plt.xticks(x_tick)
# 2.2 添加网格
plt.grid()
# 3. 显示图像
plt.show()
思路:
# 1. 先判断总共有哪些电影类别,要去重。每部电影的多个类别用 , 分割
temp_list = [i.split(',') for i in movie['Genre']] # 将每部电影的类别分割,并放入列表。
genre_list = np.unique([i for j in temp_list for i in j]) # 获取到总的电影类别列表。
# 2. 以电影类别作为列索引,创建全为0的DataFrame
temp_df = pd.DataFrame(np.zeros([movie.shape[0], genre_list.shape[0]]), columns=genre_list)
# 3. 遍历每部电影,并根据每部电影的类别 使其对于列加1
for i in range(movie.shape[0]):
# 其中 temp_list[0] = ['Action', 'Adventure', 'Sci-Fi']
# 所以这行代码是让每部电影对应类别的列值 +1
temp_df.loc[i, temp_list[i]] = 1
# 4. 每列求和并从小到大排序
result = temp_df.sum().sort_values()
# 5. 绘图
result.plot(kind='bar', figsize=(15, 6), fontsize=20, colormap='cool')