1.处理缺失数据
总体流程是这样的,
归根在于如何处理NAN,接下来详细赘述
1.1. 处理缺失值的相关函数
- 判断缺失值
pd.isnull(df)
:用于判断 DataFramedf
中的元素是否为缺失值(NaN ),返回一个与df
形状相同的布尔型 DataFrame,元素为True
表示对应位置是缺失值。pd.notnull(df)
:与pd.isnull(df)
相反,判断 DataFramedf
中的元素是否不是缺失值,返回布尔型 DataFrame,元素为True
表示对应位置不是缺失值 。- 之后利用np.all()或者np.any就可以知道数据中是否存在NaN。
- 删除缺失值
dropna(axis='rows')
:用于删除存在缺失值的行(当axis='rows'
时 )或列(当axis='columns'
时 )。默认axis='rows'
,不会修改原数据,需要接收返回值。例如new_df = df.dropna()
,将返回删除缺失值行后的新 DataFrame。
- 填充缺失值
fillna(value, inplace=False)
:用指定的value
填充缺失值。inplace=False
(默认 )时,不修改原数据,返回填充后的新对象;inplace=True
时,直接在原数据上进行填充修改。例如df.fillna(0, inplace=True)
,将把df
中的缺失值都填充为 0 。
- 替换数据
replace(to_replace, value)
:用于替换数据。to_replace
是要被替换的值,value
是替换后的值。例如df.replace('?', np.nan)
,将把df
中所有的'?'
替换为NaN
。
1.2. 电影数据的缺失值处理示例
- 判断缺失值是否存在
import pandas as pd
# 读取电影数据
movie = pd.read_csv("./data/IMDB-Movie-Data.csv")
# 使用pd.notnull判断
not_null_result = pd.notnull(movie)
#返回一个和movie的shape一样的数组,全是布尔型,True为不空,False为空,
#只要结合np.all即可判断是否有空,如果有就是返回False
np.all(not_null_result)
# 使用pd.isnull判断#另一种方法
is_null_result = pd.isnull(movie)
#返回一个和movie的shape一样的数组,全是布尔型,True为不空,False为空,
#只要结合np.any即可判断是否有空,如果有就是返回True
- 存在缺失值且为
np.nan
时的处理- 删除缺失值
# 不修改原数据
new_movie = movie.dropna()
# 也可重新赋值给原变量
movie = movie.dropna()
- 填充缺失值
# 填充'Revenue (Millions)'列的缺失值为该列均值
movie['Revenue (Millions)'].fillna(movie['Revenue (Millions)'].mean(), inplace=True)
# 遍历填充所有存在缺失值的列
for i in movie.columns:if np.all(pd.notnull(movie[i])) == False:movie[i].fillna(movie[i].mean(), inplace=True)
- 缺失值不是
np.nan
时的处理
import pandas as pd
import numpy as np
# 读取数据(假设数据中有'?'表示缺失值)
wis = pd.read_csv("https://archive.ics.uci.edu/ml/machine-learning-databases/breast-cancer")
# 替换'?'为np.nan
wis = wis.replace(to_replace='?', value=np.nan)
# 后续再进行缺失值填充等操作,例如填充某列缺失值为均值
wis['column_name'].fillna(wis['column_name'].mean(), inplace=True)
1.3. 处理缺失值的步骤总结
- 统一缺失值形式:若存在缺失值但不是
np.nan
的形式,使用data.replace(to_replace, value=np.nan)
将其替换为np.nan
。 - 判断是否存在缺失值:使用
np.all(pd.notnull(data))
,若结果为True
,表示不存在缺失值,不做处理;若为False
,表示存在缺失值,继续下一步。 - 填充缺失值:使用统计学指标值(如均值
mean
、中位数median
等 )对缺失值进行填充,如data['column_name'].fillna(data['column_name'].mean(), inplace=True)
。
2.数据的离散化
2.1. 为什么要离散化
连续属性离散化目的是简化数据结构,减少给定连续属性值个数,常作为数据挖掘工具。比如在分析用户年龄与消费习惯时,年龄是连续属性,将年龄离散化为儿童、青年、中年、老年等区间,可更方便分析不同年龄段消费特征。
2.2. 什么是数据的离散化
连续属性离散化是在连续属性值域上划分若干离散区间,用不同符号或整数值代表落在各子区间的属性值。例如原始身高数据 165, 174, 160, 180, 159, 163, 192, 184 ,按 150 - 165, 165 - 180, 180 - 195 划分区间,可标记为矮、中、高类别。
2.3. 具体实现
- 读取股票数据
import pandas as pd
# 读取股票数据
data = pd.read_csv("./data/stock_day.csv")
# 筛选出p_change数据
p_change = data['p_change']
此代码先读取股票 CSV 文件数据,再提取出涨跌幅数据p_change
,为后续离散化操作准备数据。
- 将股票涨跌幅数据进行分组
- 等频分组(
qcut
)
- 等频分组(
# 自行分组,将数据分为10组
qcut = pd.qcut(p_change, 10)
# 计算分到每个组数据个数
qcut.value_counts()
pd.qcut(data, q)
中,data
是要分组的数据(这里是p_change
),q
是要划分的组数(这里为 10 ),它会使每组数据数量尽量相等。value_counts()
用于统计每组数据个数,经常结合离散化一起使用
- 自定义区间分组(
cut
)
# 自己指定分组区间
bins = [-100, -7, -5, -3, 0, 3, 5, 7, 100]
p_counts = pd.cut(p_change, bins)
pd.cut(data, bins)
中,data
是数据,bins
是自定义区间边界列表(这里bins
指定了具体区间 ),按此区间对数据进行分组。也经常结合value_counts()一起使用。
现在我们就通过统计个数得到了一下形式
接下来为了精准的表示每个样本在那个区间可以使用一下编码(one-hot/哑变量)
- 股票涨跌幅分组数据变成 one - hot 编码
- one - hot 编码概念:把每个类别生成一个布尔列,每个样本只有一列取值为 1 ,其他为 0 ,又称热编码。例如类别有 Human、Penguin、Octopus、Alien ,编码后每个样本对应这些类别列,只有所属类别列值为 1 。这样又多了四个特征,每个样本表示唯一。
- 实现代码
- one - hot 编码概念:把每个类别生成一个布尔列,每个样本只有一列取值为 1 ,其他为 0 ,又称热编码。例如类别有 Human、Penguin、Octopus、Alien ,编码后每个样本对应这些类别列,只有所属类别列值为 1 。这样又多了四个特征,每个样本表示唯一。
# 得出one-hot编码矩阵
dummies = pd.get_dummies(p_counts, prefix="rise")
pandas.get_dummies(data, prefix=None)
中,data
可以是类似数组、Series 或 DataFrame(这里是分组后的p_counts
),prefix
是分组名字(这里为rise
),生成 one - hot 编码矩阵,方便后续机器学习模型处理。这是最后结果。
3.数据的合并
如果数据由多张表构成,需要进行数据合并
1. pd.concat
实现数据合并
- 函数语法及参数
pd.concat(objs, axis=0, join='outer', ignore_index=False)
objs
:必填参数,是一个由 DataFrame 或 Series 组成的列表,如[data1, data2]
,表示要合并的对象。axis
:可选参数,用于指定合并的轴方向。axis=0
(默认值)表示按列索引合并(纵向合并 ),即上下拼接;axis=1
表示按行索引合并(横向合并 ),即左右拼接。join
:可选参数,指定合并时的连接方式,有'outer'
(默认值,取并集 )和'inner'
(取交集 )两种。ignore_index
:可选参数,布尔值,默认为False
。若为True
,合并后将重新生成行索引。
- 代码示例
2. pd.merge
实现数据合并
- 函数语法及参数
pd.merge(left, right, how='inner', on=None, left_on=None, right_on=None)
left
:必填参数,第一个 DataFrame。right
:必填参数,第二个 DataFrame。how
:可选参数,指定连接方式,常见取值有:'inner'
:内连接(默认值),使用左右 DataFrame 键的交集。'left'
:左连接,使用左 DataFrame 的键。'right'
:右连接,使用右 DataFrame 的键。'outer'
:外连接,使用左右 DataFrame 键的并集。
on
:可选参数,指定用于合并的共同键列。要求左右 DataFrame 都存在该列。left_on
:可选参数,指定左 DataFrame 用于合并的键列。right_on
:可选参数,指定右 DataFrame 用于合并的键列。
- 代码示例
import pandas as pd
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']})
# 默认内连接
result_inner = pd.merge(left, right, on=['key1', 'key2'])
# 左连接
result_left = pd.merge(left, right, how='left', on=['key1', 'key2'])
# 右连接
result_right = pd.merge(left, right, how='right', on=['key1', 'key2'])
print("内连接结果:\n", result_inner)
print("左连接结果:\n", result_left)
print("右连接结果:\n", result_right)
此代码先创建了两个 DataFrame left
和right
,然后分别展示了内连接、左连接和右连接的合并结果。
拓展一下连接:
其实和数据库的一样,内连接就是筛选数据,没有相同的键值的数据被排除
外连接就是分别遍历左右表,如果另一个表有相同的键值的全部列出来,直到左右两表被全部遍历完。左连接和右连接其实是特殊的外连接,就是只遍历左表或者右表。
4.交叉表和透视表(交叉表是一列特征在另一列特征上的数量,透视表是占比但其实进过计算交叉表除以和还是透视表)
案例分析
1 数据准备
- 提取日期对应的星期数
import pandas as pd
import numpy as np
# 假设data是包含股票数据的DataFrame,其索引为日期
date = pd.to_datetime(data.index).weekday
data['week'] = date
pd.to_datetime()
将索引的日期字符串转换为日期时间格式,.weekday
方法获取对应的星期数(0 代表星期一,1 代表星期二,以此类推 ),并将其作为新列week
添加到data
中。
内容拆解:
47行数据不可以用,所以要转化为可以使用的数据,接下来就可以取出对应的年月日了
- 将涨跌幅数据分类
data['posi_neg'] = np.where(data['p_change'] > 0, 1, 0)
np.where()
函数根据条件判断,当data['p_change']
大于 0 时,对应位置赋值为 1 (代表涨幅为正 ),否则赋值为 0 (代表涨幅为负或持平 ),并将结果存储在新列posi_neg
中,用来形成交叉表。
- 交叉表计算
count = pd.crosstab(data['week'], data['posi_neg'])
pd.crosstab()
函数根据data['week']
和data['posi_neg']
生成交叉表,统计每个星期数下涨跌幅为正(1 )和为负或持平(0 )的天数。
结果为现在就是交叉表了,我们展示占比(也就是透视表)
- 计算比例
sum = count.sum(axis=1).astype(np.float32)
pro = count.div(sum, axis=0)
count.sum(axis=1)
计算交叉表中每一行(每个星期数 )的总数,.astype(np.float32)
将数据类型转换为 32 位浮点数。count.div(sum, axis=0)
进行除法运算,axis=0
表示按列方向,将交叉表的每一列除以对应的行总数,得到每个星期数下涨跌幅为正和为负或持平的比例。pro如下图:
2 查看效果
import matplotlib.pyplot as plt
pro.plot(kind='bar', stacked=True)
plt.show()
pro.plot(kind='bar', stacked=True)
使用matplotlib
库绘制堆叠柱状图,kind='bar'
指定为柱状图类型,stacked=True
表示堆叠显示,便于直观对比每个星期数下涨跌幅正负的比例情况。plt.show()
显示绘制的图形。如下图
如不堆叠显示,图会如下:
3.其实我们可以直接得到一列数据的占比,使用透视表即可:
2.3 使用pivot_table
(透视表)实现
- 函数语法及参数
pivot_table(data, values=None, index=None, columns=None, aggfunc='mean', fill_value=None, margins=False)
data
:必填参数,为要操作的 DataFrame 对象,这里是包含股票数据及相关衍生列(如week
、p_n
等 )的data
。values
:可选参数,接收列名组成的列表,指定要进行聚合计算的列。这里['posi_neg']
或["p_n"]
表示对涨跌幅正负标记列进行操作。index
:可选参数,指定用于分组的索引列,这里'week'
表示按星期数分组。columns
:可选参数,用于指定列分组依据,若不指定则不进行列方向分组。aggfunc
:可选参数,默认'mean'
,用于指定聚合函数。常见的有'sum'
(求和)、'count'
(计数)等。这里默认求均值,对于 0 和 1 组成的涨跌幅正负标记列,均值就相当于涨幅为正的比例。fill_value
:可选参数,用于指定填充缺失值的值。margins
:可选参数,布尔值,默认为False
,若为True
会添加总计行和总计列。
- 代码示例
import pandas as pd
# 假设data是包含股票数据及相关衍生列(如week、p_n等)的DataFrame
result = data.pivot_table(["p_n"], index="week")
print(result)
此代码通过pivot_table
函数,以week
列为索引,对p_n
列(涨跌幅正负标记列 )进行聚合操作(默认求均值 ),直接得到每个星期数下涨幅为正的比例,相较于之前交叉表计算等步骤更为简洁。结果如下:
5.分组与聚合(两个类似于数据库,经常结合在一起使用,只有分组没有聚合只会返回一个对象没有意义。)
一个简单的图:
分组 API
DataFrame.groupby
函数
- 函数语法及参数
DataFrame.groupby(key, as_index=True)
key
:必填参数,用于指定分组的列数据,可以是单个列名,也可以是列名组成的列表。比如['color']
,表示按color
列进行分组。as_index
:可选参数,布尔值,默认为True
。当as_index=True
时,分组的键(这里是color
列的值 )会作为结果的索引;当as_index=False
时,分组的键会作为结果的一列保留,数据结构保持不变。
- 案例讲解
- 数据准备
import pandas as pd
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]})
此代码创建了一个 DataFrame col
,包含color
(颜色)、object
(物品)、price1
和price2
(价格)四列数据。
- 分组聚合操作
as_index=True
情况(默认 )
# 分组,求平均值
col.groupby(['color'])['price1'].mean()
col['price1'].groupby(col['color']).mean()
这两种写法等价,都是按color
列对price1
列进行分组并求平均值。运行后,color
列的值作为索引,得到不同颜色对应的price1
平均值:
color
green 2.025
red 2.380
white 5.560
Name: price1, dtype: float64
as_index=False
情况
col.groupby(['color'], as_index=False)['price1'].mean()
此代码按color
列对price1
列进行分组并求平均值,同时设置as_index=False
,此时color
列作为结果的一列保留,得到:
color price1
0 green 2.025
1 red 2.380
2 white 5.560
通过groupby
函数结合聚合函数(如mean
),可以方便地对数据进行分组聚合操作,分析不同组别的数据特征 。
为什么要在group后边加上具体的列呢?
明确聚合对象
groupby
函数实现分组后,需要对特定的列进行聚合计算(这里是求平均值,使用mean
函数 )。col
这个 DataFrame 可能包含多列数据(如案例中还有object
、price2
等列 ),通过指定['price1']
,就明确告诉程序是要对price1
这一列的数据进行聚合操作。如果不指定,程序就不知道具体要对哪列数据进行后续的聚合计算,会引发错误。符合逻辑操作流程
分组聚合操作的逻辑是先分组,然后对组内的某列或某些列数据进行聚合。比如在这个案例中,我们想知道不同颜色对应的
price1
价格的平均值,所以要指定price1
列。如果我们想分析price2
列,就需要指定['price2']
,如col.groupby(['color'])['price2'].mean()
。
注意,仔细观察两种方法的不同之处,group里的代码一个直接用列名一个不能:
在
col['price1'].groupby(col['color']).mean()
这种针对 Series 对象进行groupby
操作的情境下,不能直接用列名。因为
col['price1']
已经是一个 Series 对象,当对它调用groupby
方法时,groupby
函数期望传入一个与该 Series 长度一致的可迭代对象(通常也是一个 Series )来确定分组规则。如果直接写列名,比如col['price1'].groupby('color').mean()
,程序会报错,因为'color'
只是一个字符串,不是与col['price1']
长度相同的可迭代对象,无法明确分组逻辑。而在基于 DataFrame 进行
groupby
操作时,像col.groupby(['color'])['price1'].mean()
,可以直接用列名(以列表形式 )来指定分组依据,这是因为 DataFrame 的groupby
方法有相应的机制去识别和处理列名列表作为分组键 。
当group中分组依据是一个列表的时候就好像是先按第一个分,然后一次分组:
首先这张图,表示了如果使用汇总聚合函数,并不需要指定列,因为只是统计个数的话不需要明确。
第二展示了列表分组规律
6.整体电影数据案例
关于问题1 很简单
关于问题2:
因为只用pandas绘图简单无法进行刻度的设置,注意这里数据是加了.values,因为如果不加他是一个series的格式,这样有索引,你需要用值所以是.values所以需要用matplotlib进行绘图结果如下:
但是发现刻度不对,我们需要对上,所以可以将数据的max与min之间的数分21个(因为是设置了20组,需要21个数),用linspace生成等距的数即可。
关于问题3
我们首先要知道所有电影的种类(因为每个电影种类很多且有重复,可以使用unique来进行去重)
接着生成一个全0列表行数为电影数列数为电影种类,之后我们可以循环每个电影对应的种类,如果是某个类型即为1,最后按列求和就可以知道具体有多少了
解释一下那个分割:首先如果不用按逗号分割列表里每个元素是这样的:
类型之间按逗号分割
所以要按逗号分割于是可以单独提出类型列表,因为i.split返回值就是列表,所以组成了列表嵌套
之后使用双循坏就可以取出每个值了,然后在去重即可,如下图:
然后生成一个按电影为行,按类型为列的dataframe即可
之后循坏完毕按列求和即可
之后排序绘图即可