机器学习之数据处理及分析库Pandas
简介
Pandas工具包是专门用作数据处理和分析的,其底层的计算其实都是由Numpy来完成,再把复杂的操作全部封装起来,使其用起来十分高效、简洁。在数据科学领域,无论哪个方向都是跟数据打交道,所以Pandas工具包是非常实用的。
数据预处理
import pandas as pd
df = pd.read_csv('./data/titanic.csv') # 读取csv文件,逗号分割,第一行当做列名
df.head(6) # 展示前6条数据 默认5
df.info() # 打印当前读取数据的部分信息,包括数据样本规模、每列特征类型与个数、整体的内存占用等。
df.index # 获取索引 就是行号
df.columns # 获取列信息 每一列的列名
df.dtypes # 每一列的类型
df.values # 获取所有行的数据
age = df['Age'] # 获取指定列
age[:5] # 获取0-4的数据
age.values[:5] # 获取0-4的值
df = df.set_index('Name') # 设置索引 默认是行号
df.head()
age = df['Age']
age['Allen, Mr. William Henry'] # 直接通过名称来查找,没对Name设置索引之前是不行的
df[['Age','Fare']][:5] # 获取指定列的指定行数据
df.iloc[0] # 获取第一行
df.iloc[0:3] # 获取[0-3)行
df.iloc[0:5,1:3] # 获取指定行的指定列
df = df.set_index('Name')
df.loc['Heikkinen, Miss. Laina'] # 根据Name查找行数据 必须先对Name设置索引
df.loc['Heikkinen, Miss. Laina','Fare'] # 查找指定行的指定列数据
df.loc['Heikkinen, Miss. Laina':'Allen, Mr. William Henry',:] # 查找Name区间内的全部列数据
df.loc['Heikkinen, Miss. Laina','Fare'] = 1000 # 对指定位置赋值
df[df['Fare'] > 40][:5] # 筛选船票价格大于 40 的乘客并展示前 5 条
df[df['Sex'] == 'male'][:5] # 筛选性别是男性的乘客并展示前 5 条
df.loc[df['Sex'] == 'male','Age'].mean() # 计算所有男性乘客的平均年龄
(df['Age'] > 70).sum() # 计算大于 70 岁的乘客的人数
data = {'country':['aaa','zvsd','ccc'],
'population':[10,12,14]}
df_data = pd.DataFrame(data) # 创建DataFrame 表示列名,value表示各个样本的实际值
pd.get_option('display.max_rows') # 获取配置
pd.set_option('display.max_rows',6) # 设置配置
pd.Series(index = range(0,100)) # 创建Series Series可以看做某一行或一列
data = [10,11,12]
index = ['a','b','c']
s = pd.Series(data = data,index = index) # 创建Series 带数据和索引
s.loc['b'] # 查询值
s.iloc[1]
s1 = s.copy()
s1['a'] = 100 # 设置值
s1.replace(to_replace = 100,value = 101,inplace = True) # 将查询到的100替换为101,原地替换
s1.index = ['a','b','d'] # 修改索引
s1.rename(index = {'a':'A'},inplace = True) # 修改指定索引 a->A
s2 = pd.Series(data = [100,110],index = ['h','k'])
s3 = s1.append(s2,ignore_index=True) # 数据拼接 ignore_index默认False,True表示结果不包含原来的索引,重新创建
del s1['A']
s1.drop(['b'],inplace = True) # 删除索引及索引对应的值
数据分析
df = pd.DataFrame([[1,2,3],[4,5,6]],index = ['a','b'],columns = ['A','B','C']) # 创建DataFrame
df.sum() # 默认axis=0,对每列计算 均值df.mean()、中位数df.median()、最大值df.max()、最小值df.min()等操作都相同
df.describe() # 展示所有数值特征的统计结果,包括数据个数、均值、标准差、最大值、最小值等信息
df.cov() # 协方差矩阵
df.corr() # 相关系数
df['Sex'].value_counts() # 统计该列属性的个数 类型为Series,类似groupby
df['Sex'].value_counts(ascending = True) # 指定顺序
df['Age'].value_counts(ascending = True,bins = 5) # 指定划分成几个组,平均分为5组 相当于将连续值进行了离散化
ages = [15,18,20,21,22,34,41,52,63,79]
bins = [10,40,80]
bins_res = pd.cut(ages,bins) # 用这3个值对数据分组,也就是(10,40],(40, 80]这两组
bins_res.labels # 当前分组结果 这个字段已deprecated,推荐使用下面的
bins_res.codes # 当前分组结果 [0, 0, 0, 0, 0, 0, 1, 1, 1, 1]
pd.value_counts(bins_res) # 各组人数
group_names = ['Yonth','Mille','Old']
pd.value_counts(pd.cut(ages,[10,20,50,80],labels=group_names)) # 自定义标签
机器学习中比拼的就是数据特征够不够好,将特征中连续值离散化可以说是常用的套路。
# 其中Category表示把钱花在什么用途上(如交通运输、家庭、娱乐等费用),Month表示统计月份,Amount表示实际的花费。
example = pd.DataFrame({'Month': ["January", "January", "January", "January", "February", "February", "February", "February", "March", "March", "March", "March"], 'Category': ["Transportation", "Grocery", "Household", "Entertainment","Transportation", "Grocery", "Household", "Entertainment","Transportation", "Grocery", "Household", "Entertainment"], 'Amount': [74., 235., 175., 100., 115., 240., 225., 125., 90., 260., 200., 120.]})
# 每个月花费在各项用途上的金额分别是多少 example_pivot为DataFrame类型
# index指定了按照什么属性来统计,columns指定了统计哪个指标,values指定了统计的实际指标值是什么
example_pivot = example.pivot(index = 'Category',columns= 'Month',values = 'Amount')
example_pivot.sum(axis = 1) # 每项花费的总额
example_pivot.sum(axis = 0) # 每月花费的总额
df = pd.read_csv('./data/titanic.csv')
# 按乘客的性别分别统计各个舱位购票的最大价格 aggfunc默认计算平均值
df.pivot_table(index = 'Sex',columns='Pclass',values='Fare',aggfunc='max')
# 按乘客的性别分别统计各个船舱等级的人数
df.pivot_table(index = 'Sex',columns='Pclass',values='Fare',aggfunc='count')
# 首先按照年龄将乘客分成两组:成年人和未成年人。再对这两组乘客分别统计不同性别的人的平均获救可能性
df['Underaged'] = df['Age'] <= 18 # 增加一列
df.pivot_table(index = 'Underaged',columns='Sex',values='Survived',aggfunc='mean')
df = pd.DataFrame({'key':['A','B','C','A','B','C','A','B','C'],'data':[0,5,10,5,10,15,10,15,20]})
df.groupby('key')['data'].aggregate(np.sum) # 根据key分组,统计data的和
df.groupby('key')['data'].mean() # 根据key分组,统计data的和 两种写法
df = pd.DataFrame({'A' : ['foo', 'bar', 'foo', 'bar','foo', 'bar', 'foo', 'foo'],'B' : ['one', 'one', 'two', 'three', 'two', 'two', 'one', 'three'],'C' : np.random.randn(8),'D' : np.random.randn(8)})
# 表示 A 在取不同 key 值时,B、C、D 中样本的数量
grouped = df.groupby('A')
grouped.count()
# 指定多个
grouped = df.groupby(['A','B'])
grouped.count()
# 分组求和
grouped = df.groupby(['A','B'])
grouped.aggregate(np.sum)
# 使用数值编号索引
grouped = df.groupby(['A','B'],as_index = False)
grouped.aggregate(np.sum)
# 设置多重索引
arrays = [['bar', 'bar', 'baz', 'baz', 'foo', 'foo', 'qux', 'qux'],['one', 'two', 'one', 'two', 'one', 'two', 'one', 'two']]
index = pd.MultiIndex.from_arrays(arrays,names = ['first','second'])
s = pd.Series(np.random.randn(8),index = index)
# 当level为0时,设置名为first的索引;当level为1时,设置名为second的索引
grouped = s.groupby(level = 0)
grouped = s.groupby(level = 'first') # 功能一样
grouped.sum()
常用函数操作
left = pd.DataFrame({'key': ['K0', 'K1', 'K2', 'K3'],
'A': ['A0', 'A1', 'A2', 'A3'],
'B': ['B0', 'B1', 'B2', 'B3']})
right = pd.DataFrame({'key': ['K0', 'K1', 'K2', 'K3'],
'C': ['C0', 'C1', 'C2', 'C3'],
'D': ['D0', 'D1', 'D2', 'D3']})
res = pd.merge(left, right, on = 'key') # 类似数据库的join
left = pd.DataFrame({'key1': ['K0', 'K1', 'K2', 'K3'],
'key2': ['K0', 'K1', 'K2', 'K3'],
'A': ['A0', 'A1', 'A2', 'A3'],
'B': ['B0', 'B1', 'B2', 'B3']})
right = pd.DataFrame({'key1': ['K0', 'K1', 'K2', 'K3'],
'key2': ['K0', 'K1', 'K2', 'K4'],
'C': ['C0', 'C1', 'C2', 'C3'],
'D': ['D0', 'D1', 'D2', 'D3']})
# # 类似数据库的内连接 默认inner,也可以取值left,right,outer
res = pd.merge(left, right, on = ['key1', 'key2'], how = 'inner')
res = pd.merge(left, right, on = ['key1', 'key2'], how = 'outer', indicator = True) # 外连接,并显示组合说明
# A B key1 key2 C D _merge
# 0 A0 B0 K0 K0 C0 D0 both
# 1 A1 B1 K1 K1 C1 D1 both
# 2 A2 B2 K2 K2 C2 D2 both
# 3 A3 B3 K3 K3 NaN NaN left_only
# 4 NaN NaN K3 K4 C3 D3 right_only
data = pd.DataFrame({'group':['a','a','a','b','b','b','c','c','c'],'data':[4,3,2,1,12,3,4,5,7]})
data.sort_values(by=['group','data'],ascending = [False,True],inplace=True) # 原地排序
data = pd.DataFrame({'k1':['one']*3+['two']*4,'k2':[3,2,1,3,3,4,4]})
data.drop_duplicates() # 去除重复(所有列值都一样)
data.drop_duplicates(subset='k1') # 只考虑某一列的重复情况,其他全部舍弃
df = pd.DataFrame({'data1':np.random.randn(5),'data2':np.random.randn(5)})
df2 = df.assign(ration = df['data1']/df['data2']) # 往数据中添加新的列
df = pd.DataFrame([range(3),[0, np.nan,0],[0,0,np.nan],range(3)]) # 创建时加入两个缺失值 NaN
df.isnull() # 判断所有缺失情况 True代表数据缺失
df.isnull().any(axis = 1) # 每一行只要有一个缺失值就为True
df.fillna(5) # 使用5填充缺失值 实际中更常使用的是均值、中位数等指标
data = pd.DataFrame({'food':['A1','A2','B1','B2','B3','C1','C2'],'data':[1,2,3,4,5,6,7]})
def food_map(series):
if series['food'] == 'A1': return 'A'
elif series['food'] == 'A2': return 'A'
elif series['food'] == 'B1': return 'B'
elif series['food'] == 'B2': return 'B'
elif series['food'] == 'B3': return 'B'
elif series['food'] == 'C1': return 'C'
elif series['food'] == 'C2': return 'C'
# 对数据中每一行都执行该操作,类似StreamAPI的map方法 axis=1
data['food_map'] = data.apply(food_map,axis = 'columns')
data = pd.DataFrame({'food':['A1','A2','B1','B2','B3',np.nan,np.nan],'data':[1,2,3,4,5,6,np.nan]})
def nan_count(columns):
columns_null = pd.isnull(columns)
null = columns[columns_null]
return len(null)
# 统计每一列NaN的个数 axis=0
data.apply(nan_count,axis = 'rows')
ts = pd.Timestamp('2017-11-24 13:23:45') # 创建一个时间戳 年月日 时分秒
ts.year
ts.month
ts.day
ts.hour
ts.minute
ts.second
# 加一个时间段
ts + pd.Timedelta(days=6, minutes=50, seconds=3, milliseconds=10, microseconds=10, nanoseconds=12)
s = pd.Series(['2017-11-24 00:00:00','2017-11-25 00:00:00','2017-11-26 00:00:00'])
ts = pd.to_datetime(s) # 字符串转日期
ts.dt.hour # 小时 其他指标类似
# 创建10条数据,每隔12小时
pd.Series(pd.date_range(start='2017-11-24',periods = 10,freq = '12H'))
# 以时间特征为索引
data = pd.read_csv('./data/flowdata.csv',index_col = 0,parse_dates = True)
data[pd.Timestamp('2012-01-01 09:00'):pd.Timestamp('2012-01-01 19:00')] # 根据时间范围取值
data['2013']
data[(data.index.hour > 8) & (data.index.hour <12)]
# 原始数据中每天都有好几条数据,但是这里想统计的是每天的平均指标,当然也可以计算其最大值、最小值,
# 只需把.mean()换成.max()或者.min()即可。
data.resample('3D').mean().head() # 每3天
data.resample('M').mean().head() # 按月进行统计
时间数据可以提取出非常丰富的特征,不仅有年、月、日等常规指标,还可以判断是否是周末、工作日、上下旬、上下班时间、节假日等特征,这些特征对数据挖掘任务都是十分有帮助的。
import pandas as pd
# 在 Notebook 中使用绘图操作需要先执行此命令
%matplotlib inline
# 虽然直接对数据执行plot()操作就可以完成基本绘制,但是,如果想要加入一些细节,就需要使用Matplotlib工具包
df = pd.DataFrame(np.random.randn(10,4).cumsum(0),index=np.arange(0,100,10),columns=['A','B','C','D'])
df.plot()
import matplotlib.pyplot as plt
# 指定子图 2 行一列的形式
fig,axes = plt.subplots(2,1)
data = pd.Series(np.random.rand(16),index=list('abcdefghijklmnop'))
#axes[0] 表示第一个子图
data.plot(ax = axes[0],kind='bar')
#axes[1] 表示第二个子图画在第一个子图下方
data.plot(ax = axes[1],kind='barh')
大数据处理技巧
使用Pandas工具包可以处理千万级别的数据量,但读取过于庞大的数据特征时,经常会遇到内存溢出等问题。这里教给大家一些大数据处理技巧,使其能够占用更少内存。
gl = pd.read_csv('game_logs.csv')
gl.head()
# 数据样本有 171907 个 (171907, 161)
gl.shape
# 指定成 deep 表示要详细地展示当前数据占用的内存
gl.info(memory_usage='deep')
# <class 'pandas.core.frame.DataFrame'>
# RangeIndex: 171907 entries, 0 to 171906
# Columns: 161 entries, date to acquisition_info
# dtypes: float64(77), int64(6), object(78)
# memory usage: 860.5 MB
输出结果显示这份数据读取进来后占用860.5 MB内存,数据类型主要有3种,其中,float64类型有77个特征,int64类型有6个特征,object类型有78个特征。
# 计算一下各种类型平均占用内存:
for dtype in ['float64','int64','object']:
selected_dtype = gl.select_dtypes(include = [dtype])
mean_usage_b = selected_dtype.memory_usage(deep=True).mean()
mean_usage_mb = mean_usage_b/1024**2
print (' 平均内存占用 ',dtype,mean_usage_mb)
平均内存占用 float64 1.2947326073279748
平均内存占用 int64 1.1241934640066964
平均内存占用 object 9.514454069016855
从结果可以发现,float64类型和int64类型平均占用内存差不多,而object类型占用的内存最多。
def mem_usage(pandas_obj):
if isinstance(pandas_obj,pd.DataFrame):
usage_b = pandas_obj.memory_usage(deep=True).sum()
else:
usage_b = pandas_obj.memory_usage(deep=True)
usage_mb = usage_b/1024**2
return '{:03.2f} MB'.format(usage_mb)
gl_int = gl.select_dtypes(include = ['int64'])
coverted_int=gl_int.apply(pd.to_numeric,downcast='integer')
print (mem_usage(gl_int))
print (mem_usage(coverted_int))
7.87MB 全部为 int64 类型时,int 类型数据内存占用量
1.80MB 向下转换后,int 类型数据内存占用量
可以看到在进行向下转换的时候,程序已经自动地选择了合适类型,再来看看内存占用情况,原始数据占用7.87MB,转换后仅占用1.80MB,大幅减少了。由于int型数据特征并不多,差异还不算太大,转换float类型的时候就能明显地看出差异了。
gl_float = gl.select_dtypes(include=['float64'])
converted_float = gl_float.apply(pd.to_numeric,downcast='float')
print(mem_usage(gl_float))
print(mem_usage(converted_float))
全部为 float64 时,float 类型数据内存占用100.99 MB
向下转换后,float 类型数据内存占用50.49MB
可以明显地发现内存节约了正好一半,通常在数据集中float类型多一些,如果对其进行合适的向下转换,基本上能节省一半内存。
gl_obj = gl.select_dtypes(include = ['object']).copy()
converted_obj = pd.DataFrame()
for col in gl_obj.columns:
num_unique_values = len(gl_obj[col].unique())
num_total_values = len(gl_obj[col])
if num_unique_values / num_total_values < 0.5:
converted_obj.loc[:,col] = gl_obj[col].astype('category')
else:
converted_obj.loc[:,col] = gl_obj[col]
print(mem_usage(gl_obj))
print(mem_usage(converted_obj))
751.64 MB
51.67 MB
首先对object类型数据中唯一值个数进行判断,如果数量不足整体的一半(此时能共用的内存较多),就执行转换操作,如果唯一值过多,就没有必要执行此操作。最终的结果非常不错,内存占用减少了很多。