Pandas世界来去自如:pandas的I/O
老生常谈,从基础来看,我们仍然关心的是pandas对于外部数据是如何交互的。
结构化数据输入输出
- read_csv 与 to_csv 是一对输入输出工具,read_csv 直接返回 pandas.DataFrame ,而 to_csv 只要执行命令即可写文件。
- read_table:功能类似
- read_fwf:操作fixed width file
- read_excel与tp_excel方便的与Excel交互
还记得上节课的例子么?
- header 表示数据中是否存在列名,如果在第零行就写零,并且开始读数据时跳过相应的行数,不存在可以写 none 。
- names 表示要用给定的列名作为最终的列名。
- encoding 表示数据集的字符编码,通常而言一份数据为了方便的进行文件传输都以utf-8作为标准。
提问:下列例子中,header = 4 , names = cnames 时,究竟能读到什么样的数据?
print(cnames)
irisdata = pd.read_csv("SIEP3_Iris.txt", header = None, name = cnames, encoding = "utf-8")
irisdata[::30]
希望了解全部参数的请移步API:
https://pandas.pydata.org/pandas-docs/stable/reference/api/pandas.read_csv.html
这里介绍一些常用参数:
- 读取处理:
- skiprows :跳过一定的行数
- nrows :仅读取一定的行数
- skipfooter :尾部有固定的行数永不读取
- skip_blank_lines :空行跳过
- 内容处理:
- sep/delimiter :分隔符很重要,常见的有逗号、空格和tab("\t")。
- na_values :指定应该被当作 na_values 的数值。
- thousands:处理数值类型时,每千位分隔符并不统一(1.234.567.89或者1,234,567,89都有可能),此时要把字符串转换成数字需要指明千位符。
- 收尾处理:
- index_col :将真实的某列(列的数目,甚至列名)当作index。
- squeeze :仅读到一列时,不再保存为pandas.DataFrame而是pandas.Series。
Excel。。。?
对于存储着极为规整数据的Excel而言,其实是没必要一定要用Excel来存数据,尽管pandas也非常友好的提供了I/O接口。
irisdata.to_excel("SIPE3_irisdata.xls", index = None, encoding = "utf-8")
irisdata_from_excel = pd.read_excel("SIPE3_irisdata.xls", header = 0, encoding = "utf-8")
irisdata_from_excel[::30]
唯一重要的参数是:sheetname = k, 标志着一个Excel的第k个sheet页将会被取出。(默认从0开始)
半结构化数据
JSON :网络传输中常用的一种数据格式。
仔细看一下,实际上这就是我们平时收集到异源数据的风格是一致的。
- 列名不能完全匹配。
- 关联键可能并不唯一。
- 元数据被保存在数据里。
json_data = [
{"name":"Wang","sal":50000,"job":"VP"},
{"name":"Zhang","job":"Manager","report":"VP"},
{"name":"Li","sal":5000,"report":"Manager"}
]
data_employee = pd.read_json(json.dumps(json_data))
data_employee_ri = data_employee.reindex(columns = ["name","job","sal","report"])
print(data_employee_ri)
数据库连接流程(Optional)
使用下列包,通过数据库配置建立Connection。
- pymysql
- pyODBC
- cx_Oracle
通过pandas.read_sql_query , read_sql_table , to_sql 进行数据库操作。
Python与数据库的交互方案有很多种,从数据分析师角度来看pandas方案比较合适,之后的讲义中会结合sql语法进行讲解。
进行MySQL数据库连接:
import pymysql
import pymysql.cursors
conn = pymysql.connect(
host = "127.0.0.1",
user = root,
password = "123456",
charset = "utf8",
cursorclass = pymysql.cursors.DictCursor
)
# pd.read_sql_query("sql",conn)
# df.to_sql("tablename",conn,flavor = "mysql")
深入Pandas数据操纵
在第一部分的基础上,数据会有更多的操纵方式:
- 通过列名、行index来取数据,结合ix、iloc灵活的获取数据的一个子集(第一部分已经介绍)
- 按记录拼接(就像Union All)或者关联(join)
- 方便的自定义函数映射
- 排序
- 缺失值处理
- 与Excel一样灵活的数据透视表
数据整合:方便灵活
横向拼接:直接DataFrame
pd.DataFrame([np.random.rand(2), np.random.rand(2), np.random.rand(2)],columns = ["c1", "c2"])
横向拼接:Concatenate
pd.concat([data_employee_ri, data_employee_ri, data_employee_ri])
纵向拼接:Merge
- 根据数据列关联,使用on关键字
- 可以指定一列或者多列
- 可以使用left_on或者right_on
pd.merge(data_employee_ri, data_employee_ri, on = "name")
########
pd.merge(data_employee_ri, data_employee_ri, on = ["name","job"])
########
# 根据index关联,可以直接使用left_index或者right_index
data_emplyee_ri.index.name = "index1"
pd.merge(data_emplyee_ri,data_emplyee_ri,left_index = "index1", right_index = "index1")
TIPS:增加how关键字,并指定
- how = “inner”
- how = “left”
- how = “right”
- how = “outer”
结合how,可看到merge基本再现了SQL应有的能力,并保持代码整洁。
DFA = pd.DataFrame(
{
"name":[u"老王",u"老张",u"老李"],
"sal":[5000,3000,1000]
}
)
print(DFA)
########
DFB = pd.DataFrame(
{
"name":[u"老王",u"老刘"],
"job":["VP","Manager"]
}
)
print(DFB)
########
# how = "left" :保留左表信息
pd.marge(DFA, DFB, on = "name", how = "left")
########
# how = "right" :保留右表信息
pd.marge(DFA, DFB, on = "name", how = "right")
########
# how = "inner" :保留两表交集信息,尽量避免出现缺失值
pd.marge(DFA, DFB, on = "name", how = "inner")
########
# how = "onter" :保留两表并集信息,优点最大程度的整合了已有信息,缺点会导致出现缺失值
pd.marge(DFA, DFB, on = "name", how = "onter")
数据清洗三剑客
接下来三个功能:map,applymap,apply功能,是绝大参数数据分析师在数据清洗这一步骤的必经之路。
他们分别的回答了以下问题:
- 我想根据一列数据新做一列数据,怎么办?(答:Series -> Series)
- 我想根据整张表数据新作整张表,怎么办?(答:DataFrame -> DataFrame)
- 我想根据很多列的数据新做一列数据,怎么办?(答:DataFrame -> Series)
不要再写什么for循环了!改变思维,提高编码和执行效率。
dataNumpy = np.asarray([
("Japan","Tokyo",4000),
("S.Korea","Seoul",1300),
("China","Beijing",9100)
])
DF = pd.DataFrame(dataNumpy,columns = ["nation","capital","GDP"])
print(DF)
Map:以相同规则将一列数据作一个映射,也就是进行相同函数的处理。
def GDP_Factorize(v):
fv = np.float64(v)
if fv > 6000.0:
return "High"
elif fv < 2000.0:
return "Low"
else:
return "Medium"
DF["GDP_Level"] = DF["GDP"].map(GDP_Factorize)
DF["NATION"] = DF.nation.map(str.upper)
print(DF)
applymap:可以对一个DataFrame里面的每一个元素像map那样全局操作。
DF.applymap(lambda x:float(x)*2 if x.isdigit() else x.upper())
apply则可以对一个DataFrame操作得到一个Series。
它会有点像我们后续介绍的agg,但是apply可以按行操作和按列操作,用axis控制即可。
DF.apply(lambda x:x["nation"] + x["capital"] + "_" + x["GDP"], axis = 1)
数据排序
- sort:按一列或者多列的值进行行级排序
- sort_index:根据index里的取值进行排序,而且可以根据axis决定是重排行还是列。
dataNumpy = np.asarray([
("Japan", "Tokyo", 4000)
("S.Korea", "Seoul", 1300)
("China", "Beijing", 9100)
])
DF = pd.DataFrame(dataNumpy, columns = ["nation", "capital", "GDP"])
print(DF)
########
DF.sort(["capotal", "nation"])
DF.sort("GDP", ascending = False)
DF.sort("GDP").sort(ascending = False)
DF.sort_index(axis = 1, ascending = True)
print(DF)
########
DF.rank()
DF.rank(ascending = False)
注意tied data(相同值)处理:
- method = “average”
- method = “min”
- method = “max”
- method = “first”
DF = pd.DataFrame(
{
"name":[u"老王",u"老刘",u"老李",u"老张"],
"sal":np.array([5000, 3000, 5000, 9000])
}
)
print(DF)
########
# DF.rank()默认使用method = "average",两条数据相等时,处理排名时大家都用平均值。
DF.sal.rank()
# method = "min",处理排名时大家都用最小值。
DF.sal.rank(method = "min")
# method = "max",处理排名时大家都用最大值。
DF.sal.rank(method = "max")
# method = "first",处理排名时谁先出现就先给谁较小的数值。
DF.sal.rank(method = "first")
缺失数据处理
DF = data_for_multi.unstack()
print(DF)
########
# 忽略缺失值
DF.mean(skipna = True)
DF.mean(skipna = False)
# 如果不想忽略缺失值,就要祭出fillna了。
DF.finllna(0).mean(axis = 1, skipna = False)
Pandas的groupby
groupby的功能类似于SQL中的group by关键字
- Split-Apply-Combine:
- split,就是按照规则排序
- Apply:通过一定的agg函数来获得pd.Series返回一个值的效果。
- Combine:把结果收集起来。
- Pandas的groupby的灵活性:
- 分组的关键字可以来自于index,也可以来自于真实的列数据。
- 分组规则可以通过一列或者多列
from IPython.display import Image
Image(filename = "SIP3_group.png")
# 分组的具体逻辑
irisdata_group = irisdata.groupby("class")
print(irisdata_group)
for level,subsetDF in irisdata_group:
print(level)
print(subsetDF)
分组可以快速实现MapReduce的逻辑 :
- Map:指定的分组的列标签,不同的值就会被扔到不同的分组处理。
- Reduce:输入多个值,返回一个值,一般可以通过agg实现,agg能够接受一个函数。
irisdata.groupby("class").agg(\
lambda x:((x - x.mean())**3).sum()*(len(x) - 0.0 )/\
(len(x) - 1.0)/(len(x) - 2.0)/(x.std() * np.sqrt((len(x) - 0.0)/(len(x) - 1.0)))**3 if len(x)>2 else None
)
irisdata.groupby("class").agg(spstat.skew)
汇总之后的广播操作
在OLAP数据库上,为了避免groupby与join的二次操作,提出了sum()over(paritition by)的 开窗操作。
在pandas中,这波操作能够进一步呗transfrom锁取代:
pd.concat([irisdata, irisdata.groupby("class").transform("mean")], axis = 1)[::20]
产生MultiIndex(多列分组)后数据透视表操作
一般来说,多列groupby的 一个副作用就是.groupby().agg()之后你的行index就已经变成了一个多列分组的分级索引。
如果我们希望达到Excel的数据透视表效果,行和列的索引自由交换,达到统计目的,究竟应该怎么做?
factor1 = np.random.randint(0, 3, 50)
factor2 = np.random.randint(0, 2, 50)
factor3 = np.random.randint(0, 3, 50)
hierindexDF = pd.DataFrame({"F1":factor1,"F2":factor2,"F3":factor3,"F4":values})
print(hierindexDF)
##########
hierindexDF_gbsum = hierindexDF.groupby(["F1","F2","F3"]).sum()
print(hierindexDF_gbsum)
# 观察 index 值
print(hierindexDF_gbsum.index)
# unstack:
# 无参数时,把最末端index置换到column上
# 有数字参数时,把指定位置的index置换到column上
# 有列表参数时,依次把特定位置的index置换到column上
hierindexDF_gbsum.unstack()
hierindexDF_gbsum.unstack(0)
hierindexDF_gbsum.unstack([2,0])
# 更进一步的,satck功能是和unstack所对应,把column上的多级索引换到index上去。
hierindexDF_gbsum.unstack([2,0]).stack([1,2])