数据读取是进行数据预处理、建模与分析的前提。不同的数据源,需要使用不同的函数读取。pandas内置了10余种数据源读取函数和对应的数据写入函数,常见的数据源有3种,分别是数据库数据、文本文件(一般文本文件和CSV文件)和Excel文件。
在生产环境中,绝大多数的数据都存储在数据库中。pandas提供了读取与存储关系型数据库数据的函数与方法。除pandas库外,还需要使用SQLAlchemy库建立对应的数据库连接。SQLAlchemy配合相应数据库的Python连接工具(例如,MySQL数据库需要安装mysqlclient或者pymysql库,Oracle数据库需要安装cx_oracle库),使用creat_engine函数,建立一个数据库连接(本篇以MySQL数据库为例)。
数据库连接:
from sqlalchemy import create_engine
## 创建一个mysql连接器,用户名为root,密码为123456
## 数据库名称为testdb,编码为utf-8
engine = create_engine('mysql+pymysql://root:@localhost:3306/testdb')
print(engine)
pandas中实现数据库读取有3个函数,read_sql,read_sql_table和read_sql_query。read_sql_table只能读取数据库的某一个表格,不能实现查询操作。read_sql_query只能实现查询操作,不能直接读取数据库中的某个表。read_sql是两种的综合。
import pandas as pd
## 使用read_sql_query查看tesdb中的数据表数目
formlist = pd.read_sql_query('show tables', con = engine)
print('testdb数据库数据表清单为:','\n',formlist)
## 使用read_sql_table读取订单详情表
detail1 = pd.read_sql_table('meal_order_detail1',con = engine)
print('使用read_sql_table读取订单详情表的长度为:',len(detail1))
## 使用read_sql读取订单详情表
detail2 = pd.read_sql('select * from meal_order_detail2',con = engine)
print('使用read_sql函数+sql语句读取的订单详情表长度为:',len(detail2))
将DataFrame写入数据库中,同样也要依赖SQLAlchemy库的create_engine函数创建数据库连接,数据存储只有一个to_sql方法。
## 使用to_sql存储orderData
detail1.to_sql('test1',con = engine,index = False, if_exists = 'replace')
## 使用read_sql读取test表
formlist1 = pd.read_sql_query('show tables',con = engine)
print('新增一个表格后testdb数据库数据表清单为:','\n',formlist1)
文本文件是一种由若干行字符构成的计算机文件,它是一种典型的顺序文件。CSV是一种用分隔符分隔的文件格式,因为其分隔符不一定是逗号,因此又被称为字符分隔文件。文件以纯文本形式存储表数据(数字和文本)。
CSV文件根据其定义也是一种文本文件。在数据读取过程中可以使用文本文件的读取函数对CSV文件进行读取。同时,如果文本文件是字符分隔文件,也可以使用读取CSV文件的函数读取。pandas提供了read_table来读取文本文件,提供了read_csv来读取CSV文件。
## 使用read_table读取订单信息表
order = pd.read_table('../data/meal_order_info.csv',sep = ',',encoding = 'gbk')
print('使用read_table读取的订单信息表的长度为:',len(order))
## 使用read_csv读取订单信息表
order1 = pd.read_csv('../data/meal_order_info.csv',encoding = 'gbk')
print('使用read_csv读取的订单信息表的长度为:',len(order1))
read_table和read_csv函数中的sep参数是指定文本的分隔符,如果分隔符指定错误,在读取数据的时候,每一行数据将连成一片。read_csv默认为",",read_table默认为制表符"Tab".
文本文件存储与读取类似,对于结构化数据可以通过pandas中的to_csv函数实现以CSV文件格式存储。
import os
print('订单信息表写入文本文件前目录内文件列表为:\n',os.listdir('../temp'))
## 将order以csv格式存储
order.to_csv('../temp/orderInfo.csv',sep = ';',index = False)
print('订单信息表写入文本文件后目录内文件列表为:\n',os.listdir('../temp'))
pandas提供了read_excel函数来读取"xls","xlsx"两种Excel文件。
## 读取user.xlsx文件
user = pd.read_excel('../data/users.xlsx')
print('客户信息表长度为:',len(user))
将文件存储为Excel文件,可以使用to_excel函数。to_excel函数和to_csv函数常用参数基本一致,区别之处在于,to_excel函数指定存储文件的文件路径参数名称为excel_writer,并没有sep参数;to_excel函数增加了一个sheetname参数,用来指定存储的Excel Sheet的名称,默认为Sheet1。
print('客户信息表写入excel文件前目录内文件列表为:\n', os.listdir('../tmp'))
user.to_excel('../tmp/userInfo.xlsx')
print('客户信息表写入excel文件后目录内文件列表为:\n',os.listdir('../tmp'))
DataFrame是非常常用的pandas对象,类似于Execl表格。完成数据读取后,数据就以DataFrame数据结构存储在内存中。
DataFrame基础属性有values、index、columns和dtypes,分别可获取元素、索引、列名和类型。
print('订单详情表的索引为:', detail.index)
print('订单详情表的所有值为:','\n', detail.values)
print('订单详情表的列名为:','\n', detail.columns)
print('订单详情表的数据类型为:','\n', detail.dtypes)
除了上述4个基本属性外,size、ndim和shape属性能够获取DataFrame的元素个数、维度数和数据形状(行列数目)。
print('订单详情表的元素个数为:', detail.size) ## 查看DataFrame的元素个数
print('订单详情表的维度数为:', detail.ndim) ## 查看DataFrame的维度数
print('订单详情表的形状为:', detail.shape) ## 查看DataFrame的形状
另外,T属性能够实现DataFrame的转置(行列转换),代码如下:
print('订单详情表转置前形状为:',detail.shape)
print('订单详情表转置后形状为为:',detail.T.shape)
DataFrame作为一种二维数据表结构,能够像数据库一样实现增删改查操作,如添加一行,删除一行,添加一列,删除一列,修改某一个值,或将在某个区间的值替换等。
## 使用访问字典方式取出orderInfo中的某一列
order_id = detail['order_id']
print('订单详情表中的order_id的形状为:','\n',order_id.shape)
## 使用访问属性方式取出orderInfo中的菜品名称列
dishes_name = detail.dishes_name
print('订单详情表中的dishes_name的形状为:',dishes_name.shape)
以上两种方法均可获得DataFrame中的某一列数据,但是使用属性的方法访问数据并不建议使用。因为多数时候数据的列名为英文,以属性方式访问某一列的形式和DataFrame属性访问,其方法和格式相同,难免存在部分列名和pandas提供的方法相同,此时会引起程序混乱,也会使代码晦涩难懂。
访问DataFrame中的某一列的某几行时,单独一列的DataFrame可以视为一个Series(另一种pandas提供的类,可以看作是只有一列的DataFrame),而访问一个Series基本和访问一个一维的ndarray相同,代码如下:
dishes_name5 = detail['dishes_name'][:5]
print('订单详情表中的dishes_name前5个元素为:','\n',dishes_name5)
orderDish = detail[['order_id','dishes_name']][:5]
print('订单详情表中的order_id和dishes_name前5个元素为:','\n',orderDish)
如果只是需要访问DataFrame某几行数据,则实现方式和上述的访问多行列相似,选择所有列,使用“:”代替即可。
order5 = detail[:][1:6]
print('订单详情表的1-6行元素为:','\n',order5)
通过DataFrame提供的方法head和tail也可以得到多行数据,但是这两种方法得到的数据都是从开始或末尾获取的连续数据。
print('订单详情表中前五行数据为','\n',detail.head())
print('订单详情表中后五个元素为:','\n',detail.tail())
# 单列切片
dishes_name1 = detail.loc[:,'dishes_name']
print('使用loc提取dishes_name列的size为:', dishes_name1.size)
dishes_name2 = detail.iloc[:,3]
print('使用iloc提取第3列的size为:', dishes_name2.size)
# 多列切片
orderDish1 = detail.loc[:,['order_id','dishes_name']]
print('使用loc提取order_id和dishes_name列的size为:', orderDish1.size)
orderDish2 = detail.iloc[:,[1,3]]
print('使用iloc提取第1和第3列的size为:', orderDish2.size)
# 花式切片
print('列名为order_id和dishes_name的行名为3的数据为:\n',detail.loc[3,['order_id','dishes_name']])
print('列名为order_id和dishes_name行名为2,3,4,5,6的数据为\n',detail.loc[2:6,['order_id','dishes_name']])
print('列位置为1和3行位置为3的数据为:\n',detail.iloc[3,[1,3]])
print('列位置为1和3行位置为2,3,4,5,6的数据为:\n',detail.iloc[2:7,[1,3]])
# 条件切片
print('detail中order_id为458的dishes_name为:\n',
detail.loc[detail['order_id']=='458',
['order_id','dishes_name']])
print('detail中order_id为458的第1,5列数据为:\n',
detail.iloc[detail['order_id']=='458',[1,5]])
print('列名为dishes_name行名为2,3,4,5,6的数据为:\n',detail.loc[2:6,'dishes_name'])
print('列位置为5,行位置为2至6的数据为:\n',detail.iloc[2:6,5])
print('列位置为5行名为2至6的数据为:', '\n',detail.ix[2:6,5])
控制ix方法需要注意以下几点:
(1)使用ix参数时,尽量保持行索引名称和行索引位置重叠,使用时就无需考虑取值区间的问题。一律为闭区间。
(2)使用列索引名称,而非列索引位置。主要用来保证代码可读性。
(3)使用列索引位置时需要注解。同样用来保证代码可读性。
(4)ix方法还有一个缺点,就是面对数据量巨大的任务的时候,其效率会低于loc和iloc方法,所以在日常的数据分析工作中建议使用loc和iloc方法来执行切片操作。
##将order_id为458的,变换为45800
detail.loc[detail['order_id']=='458','order_id'] = '45800'
print('更改后detail中order_id为458的order_id为:\n',
detail.loc[detail['order_id']=='458','order_id'])
print('更改后detail中order_id为45800的order_id为:\n',
detail.loc[detail['order_id']=='45800','order_id'])
detail['payment'] = detail['counts']*detail['amounts']
print('detail新增列payment的前五行为:','\n',detail['payment'].head())
print('删除1-10行前detail的长度为:',len(detail))
detail.drop(labels = range(1,11),axis = 0,inplace = True)
print('删除1-10行后detail的列索引为:',len(detail))
多数情况下,对时间类型数据进行分析的前提是将原本为字符串的时间转换为标准时间。pandas继承了NumPy库和detetime库的时间相关模块,提供了6种时间相关的类,如下表。
类名称 | 说明 |
---|---|
Timestamp | 最基础的时间类。表示某个时间点 |
Period | 表示某个单个时间跨度,或者某个时间段,例如某一天、某一小时等 |
Timedelta | 表示不同单位的时间,如1d、1.5h、3min等,而非具体某个时间段 |
DatatimeIndex | 一组Timestamp构成的Index,可以用来作为Series或者DataFrame的索引 |
PeriodtimeIndex | 一组Period构成的Index,可以用来作为Series或者DataFrame的索引 |
TimedeltaIndex | 一组Timedelta构成的Index,可以用来作为Series或者DataFrame的索引 |
Timestamp时间类中最基础的,最常用的。多数情况下,会将与时间相关的字符串转换成为Timestamp。pandas提供了to_datatime函数。
print('进行转换前订单信息表lock_time的类型为:', order['lock_time'].dtypes)
order['lock_time'] = pd.to_datetime(order['lock_time'])
print('进行转换后订单信息表lock_time的类型为:', order['lock_time'].dtypes)
Timestamp类型的时间是有限制的,作者的计算机最早只能够表示至1677年9月21日,最晚只能表示2262年4月11日。
还可以将数据单独提取出来,将其转换为DatatimeIndex或者PeriodIndex。
dateIndex = pd.DatetimeIndex(order['lock_time'])
print('转换为DatetimeIndex后数据的类型为:\n',type(dateIndex))
periodIndex = pd.PeriodIndex(order['lock_time'],freq = 'S')
print('转换为DatetimeIndex后数据的类型为:\n',type(periodIndex))
year1 = [i.year for i in order['lock_time']]
print('lock_time中的年份数据前5个为:',year1[:5])
month1 = [i.month for i in order['lock_time']]
print('lock_time中的月份数据前5个为:',month1[:5])
day1 = [i.day for i in order['lock_time']]
print('lock_time中的日期数据前5个为:',day1[:5])
weekday1 = [i.weekday_name for i in order['lock_time']]
print('lock_time中的星期名称数据前5个为:',weekday1[:5])
print('dateIndex中的星期名称数据前5个为:\n',dateIndex.weekday_name[:5])
print('periodIndex中的星期标号数据前5个为:',periodIndex.weekday[:5])
## 将lock_time数据向后平移一天
time1 = order['lock_time']+pd.Timedelta(days = 1)
print('lock_time在加上一天前前5行数据为:\n',order['lock_time'][:5])
print('lock_time在加上一天前前5行数据为:\n',time1[:5])
``
```python
timeDelta = order['lock_time'] - pd.to_datetime('2017-1-1')
print('lock_time减去2017年1月1日0点0时0分后的数据:\n',timeDelta[:5])
print('lock_time减去time1后的数据类型为:',timeDelta.dtypes)
groupby方法的参数及其说明——by参数的特别说明:
detail = pd.read_sql_table('meal_order_detail1',con = engine)
detailGroup = detail[['order_id','counts','amounts']].groupby(by ='order_id')
print('分组后的订单详情表为:',detailGroup)
print('订单详情表分组后前5组每组的均值为:\n', detailGroup.mean().head())
print('订单详情表分组后前5组每组的标准差为:\n', detailGroup.std().head())
print('订单详情表分组后前5组每组的大小为:','\n', detailGroup.size().head())
agg,aggregate方法都支持对每个分组应用某函数,包括Python内置函数或自定义函数。同时这两个方法能够也能够直接对DataFrame进行函数应用操作。
在正常使用过程中,agg函数和aggregate函数对DataFrame对象操作时功能几乎完全相同,因此只需要掌握其中一个函数即可。它们的参数说明如下表。
(1)可以使用agg方法一次求出当前数据中所有菜品销量和售价的总和与均值,如:
detail[['counts','amounts']].agg([np.sum,np.mean]))
(2)对于某个字段希望只做求均值操作,而对另一个字段则希望只做求和操作,可以使用字典的方式,将两个字段名分别作为key,然后将NumPy库的求和与求均值的函数分别作为value,如:
detail.agg({'counts':np.sum,'amounts':np.mean}))
(3)在某些时候还希望求出某个字段的多个统计量,某些字段则只需要求一个统计量,此时只需要将字典对应key的value变为列表,列表元素为多个目标的统计量即可,如:
detail.agg({'counts':np.sum,'amounts':[np.mean,np.sum]}))
apply方法类似agg方法能够将函数应用于每一列。不同之处在于apply方法相比agg方法传入的函数只能够作用于整个DataFrame或者Series,而无法像agg一样能够对不同字段,应用不同函数获取不同结果。
print('订单详情表的菜品销量与售价的均值为:\n',detail[['counts','amounts']].apply(np.mean))
使用apply方法对GroupBy对象进行聚合操作其方法和agg方法也相同,只是使用agg方法能够实现对不同的字段进行应用不同的函数,而apply则不行。
print('订单详情表分组后前3组每组的均值为:','\n', detailGroup.apply(np.mean).head(3))
print('订单详情表分组后前3组每组的标准差为:','\n', detailGroup.apply(np.std).head(3))
transform方法能够对整个DataFrame的所有元素进行操作。且transform方法只有一个参数“func”,表示对DataFrame操作的函数。
print('订单详情表的菜品销量与售价的两倍为:\n',detail[['counts','amounts']].transform(lambda x:x*2).head(4))
同时transform方法还能够对DataFrame分组后的对象GroupBy进行操作,可以实现组内离差标准化等操作。
print('订单详情表分组后实现组内离差标准化后前五行为:\n',
detailGroup.transform(lambda x:(x.mean()-x.min())/(x.max()-x.min())).head())
若在计算离差标准化的时候结果中有NaN,这是由于根据离差标准化公式,最大值和最小值相同的情况下分母是0。而分母为0的数在Python中表示为NaN。
pivot_table函数主要的参数调节:
detail = pd.read_sql_table('meal_order_detail1',con = engine)
detailPivot = pd.pivot_table(detail[['order_id','counts','amounts']],
index = 'order_id')
print('以order_id作为分组键创建的订单透视表为:\n',detailPivot.head())
crosstab函数:
detailCross = pd.crosstab(
index=detail['order_id'],
columns=detail['dishes_name'],
values = detail['counts'],aggfunc = np.sum)
print('以order_id和dishes_name为分组键\
counts为值的透视表前5行5列为:\n',detailCross.iloc[:5,:5])