前言:
因为平时的分析工作中常用到的python的一些类库的用户,陆陆续续会作为笔记形式记录下来,也算是给自己做个备忘录。本文聚焦于描述基础用法。
读取CSV文件
直接从JDBC处理,效率太低了,因此对于大数据量的处理,一般是用文件来做的。有一个很大的几个G的文件,没办法一次处理,那么就分批次处理,一次处理1百万行,接着处理下1百万行,慢慢地总是能处理完的。
# 使用类似迭代器的方式
data=pd.read_csv(file,chunksize=1000000)
for sub_df in data:
print('do something in sub_df here')
索引
Series和DataFrame都是有索引的,索引的好处是快速定位,在涉及到两个Series或DataFrame时可以根据索引自动对齐,比如日期自动对齐,这样可以省去很多事。
缺失值
pd.isnull(obj)
obj.isnull()
将字典转成数据框,并赋予列名,索引
DataFrame(data, columns=['col1','col2','col3'...],
index = ['i1','i2','i3'...])
查看列名
DataFrame.columns
查看索引
DataFrame.index
重建索引
obj.reindex(['a','b','c','d','e'...], fill_value=0]
#按给出的索引顺序重新排序,而不是替换索引。如果索引没有值,就用0填充
#就地修改索引
data.index=data.index.map(str.upper)
列顺序重排(也是重建索引)
DataFrame.reindex[columns=['col1','col2','col3'...])`
#也可以同时重建index和columns
DataFrame.reindex[index=['a','b','c'...],columns=['col1','col2','col3'...])
重建索引的快捷键
DataFrame.ix[['a','b','c'...],['col1','col2','col3'...]]
重命名轴索引
data.rename(index=str.title,columns=str.upper)
#修改某个索引和列名,可以通过传入字典
data.rename(index={'old_index':'new_index'},
columns={'old_col':'new_col'})
查看某一列
DataFrame['state'] 或 DataFrame.state
查看某一行
需要用到索引
DataFrame.ix['index_name']
添加或删除一列
DataFrame['new_col_name'] = 'char_or_number'
#删除行
DataFrame.drop(['index1','index2'...])
#删除列
DataFrame.drop(['col1','col2'...],axis=1)
#或
del DataFrame['col1']
DataFrame选择子集
类型 说明
obj[val] 选择一列或多列
obj.ix[val] 选择一行或多行
obj.ix[:,val] 选择一列或多列
obj.ix[val1,val2] 同时选择行和列
reindx 对行和列重新索引
icol,irow 根据整数位置选取单列或单行
get_value,set_value 根据行标签和列标签选择单个值
针对series
obj[['a','b','c'...]]
obj['b':'e']=5
针对dataframe
#选择多列
dataframe[['col1','col2'...]]
#选择多行
dataframe[m:n]
#条件筛选
dataframe[dataframe['col3'>5]]
#选择子集
dataframe.ix[0:3,0:5]
dataframe和series的运算
会根据 index 和 columns 自动对齐然后进行运算,很方便啊
方法 说明
add 加法
sub 减法
div 除法
mul 乘法
#没有数据的地方用0填充空值
df1.add(df2,fill_value=0)
# dataframe 与 series 的运算
dataframe - series
规则是:
-------- -------- |
| | | | |
| | -------- |
| | |
| | v
--------
#指定轴方向
dataframe.sub(series,axis=0)
规则是:
-------- ---
| | | | ----->
| | | |
| | | |
| | | |
-------- ---
apply函数
f=lambda x:x.max()-x.min()
#默认对每一列应用
dataframe.apply(f)
#如果需要对每一行分组应用
dataframe.apply(f,axis=1)
当然,也可以写到一行里面的
排序和排名
#默认根据index排序,axis = 1 则根据columns排序
dataframe.sort_index(axis=0, ascending=False)
# 根据值排序
dataframe.sort_index(by=['col1','col2'...])
#排名,给出的是rank值
series.rank(ascending=False)
#如果出现重复值,则取平均秩次
#在行或列上面的排名
dataframe.rank(axis=0)
描述性统计
方法 说明
count 计数
describe 给出各列的常用统计量
min,max 最大最小值
argmin,argmax 最大最小值的索引位置(整数)
idxmin,idxmax 最大最小值的索引值
quantile 计算样本分位数
sum,mean 对列求和,均值
mediam 中位数
mad 根据平均值计算平均绝对离差
var,std 方差,标准差
skew 偏度(三阶矩)
Kurt 峰度(四阶矩)
cumsum 累积和
Cummins,cummax 累计组大致和累计最小值
cumprod 累计积
diff 一阶差分
pct_change 计算百分数变化
唯一值,值计数,成员资格
obj.unique()
obj.value_count()
obj.isin(['b','c'])
处理缺失值
# 过滤缺失值
# 只要有缺失值就丢弃这一行
dataframe.dropna()
#要求全部为缺失才丢弃这一行
dataframe.dropna(how='all')
# 根据列来判断
dataframe.dropna(how='all',axis=1)
# 填充缺失值
#1.用0填充
df.fillna(0)
#2.不同的列用不同的值填充
df.fillna({1:0.5, 3:-1})
#3.用均值填充
df.fillna(df.mean())
# 此时axis参数同前面
将列转成行索引
df.set_index(['col1','col2'...])
数据清洗,重塑
合并数据集
# 取 df1,df2 都有的部分,丢弃没有的
# 默认是inner的连接方式
pd.merge(df1,df2, how='inner')
#如果df1,df2的连接字段名不同,则需要特别指定
pd.merge(df1,df2,left_on='l_key',right_on='r_key')
#其他的连接方式有 left,right, outer等。
# 如果dataframe是多重索引,根据多个键进行合并
pd.merge(left, right, on=['key1','key2'],how = 'outer')
#合并后如果有重复的列名,需要添加后缀
pd.merge(left, right, on='key1', suffixes=('_left','_right'))
索引上的合并
#针对dataframe中的连接键不是列名,而是索引名的情况。
pd.merge(left, right, left_on = 'col_key', right_index=True)
#即左边的key是列名,右边的key是index。
#多重索引
pd.merge(left, right, left_on=['key1','key2'], right_index=True)
dataframe的join方法
#实现按索引合并。
#其实这个join方法和数据库的join函数是以一样的理解
left.join(right, how='outer')
#一次合并多个数据框
left.join([right1,right2],how='outer')
轴向连接(更常用)
连接:concatenation
绑定:binding
堆叠:stacking
列上的连接
np.concatenation([df1,df2],axis=1) #np包
pd.concat([df1,df2], axis=1) #pd包
#和R语言中的 cbind 是一样的
#如果axis=0,则和 rbind 是一样的
#索引对齐,没有的就为空
# join='inner' 得到交集
pd.concat([df1,df2], axis=1, join='innner')
# keys 参数,还没看明白
# ignore_index=True,如果只是简单的合并拼接而不考虑索引问题。
pd.concat([df1,df2],ignore_index=True)
合并重复数据
针对可能有索引全部或者部分重叠的两个数据集
填充因为合并时索引赵成的缺失值
where函数
#where即if-else函数
np.where(isnull(a),b,a)
combine_first方法
#如果a中值为空,就用b中的值填补
a[:-2].combine_first(b[2:])
#combine_first函数即对数据打补丁,用df2的数据填充df1中的缺失值
df1.combine_first(df2)
重塑层次化索引
stact:将数据转为长格式,即列旋转为行
unstack:转为宽格式,即将行旋转为列
result=data.stack()
result.unstack()
长格式转为宽格式
pivoted = data.pivot('date','item','value')
#前两个参数分别是行和列的索引名,最后一个参数则是用来填充dataframe的数据列的列名。如果忽略最后一个参数,得到的dataframe会带有层次化的列。
透视表
table = df.pivot_table(values=["Price","Quantity"],
index=["Manager","Rep"],
aggfunc=[np.sum,np.mean],
margins=True))
#values:需要对哪些字段应用函数
#index:透视表的行索引(row)
#columns:透视表的列索引(column)
#aggfunc:应用什么函数
#fill_value:空值填充
#margins:添加汇总项
#然后可以对透视表进行筛选
table.query('Manager == ["Debra Henley"]')
table.query('Status == ["pending","won"]')
移除重复数据
# 判断是否重复
data.duplicated()
#移除重复数据
data.drop_duplicated()
#对指定列判断是否存在重复值,然后删除重复数据
data.drop_duplicated(['key1'])
交叉表
是一种用于计算分组频率的特殊透视表.
注意,只对离散型的,分类型的,字符型的有用,连续型数据是不能计算频率这种东西的。
pd.crosstab(df.col1, df.col2, margins=True)
类似vlookup函数
利用函数或映射进行数据转换
#1.首先定义一个字典
meat_to_animal={
'bacon':'pig',
'pulled pork':'pig',
'honey ham':'cow'
}
#2.对某一列应用一个函数,或者字典,顺便根据这一列的结果创建新列
data['new_col']=data['food'].map(str.lower).map(meat_to_animal)
替换值
data.replace(-999,np.na)
#多个值的替换
data.replace([-999,-1000],np.na)
#对应替换
data.replace([-999,-1000],[np.na,0])
#对应替换也可以传入一个字典
data.replace({-999:np.na,-1000:0})
离散化
#定义分割点
bins=[20,40,60,80,100]
#切割
cats = pd.cut(series,bins)
#查看标签
cats.labels
#查看水平(因子)
cats.levels
#区间计数
pd.value_count(cats)
#自定义分区的标签
group_names=['youth','youngAdult','MiddleAge','Senior']
pd.cut(ages,bins,labels=group_names)
分位数分割
data=np.random.randn(1000)
pd.qcut(data,4) #四分位数
#自定义分位数,包含端点
pd.qcut(data,[0,0.3,0.5,0.9,1])
异常值
#查看各个统计量
data.describe()
#对某一列
col=data[3]
col[np.abs(col)>3]
#选出全部含有“超过3或-3的值的行
data[(np.abs(data)>3).any(1)]
#异常值替换
data[np.abs(data)>3]=np.sign(data)*3
抽样
#随机抽取k行
df.take(np.random.permutation(len(df))[:k])
#随机抽取k行,但是k可能大于df的行数
#可以理解为过抽样了
df.take(np.random.randint(0,len(df),size=k))
数据摊平处理
相当于将类别属性转成因子类型,比如是否有车,这个字段有3个不同的值,有,没有,过段时间买,那么将会被编码成3个字段,有车,没车,过段时间买车,每个字段用0-1二值填充变成数值型。
#对摊平的数据列增加前缀
dummies = pd.get_dummies(df['key'],prefix='key')
#将摊平产生的数据列拼接回去
df[['data1']].join(dummies)
字符串操作
# 拆分
strings.split(',')
#根据正则表达式切分
re.split('\s+',strings)
# 连接
'a'+'b'+'c'...
或者
'+'.join(series)
# 判断是否存在
's' in strings`
strings.find('s')
# 计数
strings.count(',')
# 替换
strings.replace('old','new')
# 去除空白字符
s.strip()
正则表达式
正则表达式需要先编译匹配模式,然后才去匹配查找,这样能节省大量的CPU时间。
re.complie:编译
findall:匹配所有
search:只返回第一个匹配项的起始和结束地址
match:值匹配字符串的首部
sub:匹配替换,如果找到就替换
#原始字符串
strings = 'sdf@153.com,dste@qq.com,sor@gmail.com'
#编译匹配模式,IGNORECASE可以在使用的时候对大小写不敏感
pattern = r'[A-Z0-9._%+-]+@[A-Z0-9.-]+\\.[A-Z]{2,4}'
regex = re.compile(pattern,flags=re.IGNORECASE)
#匹配所有
regex.findall(strings)
#使用search
m = regex.search(strings) #获取匹配的地址
strings[m.start():m.end()]
#匹配替换
regex.sub('new_string', strings)
根据模式再切分
将模式切分,也就是将匹配到的进一步切分,通过pattern中的括号实现.
pattern = r'([A-Z0-9._%+-]+)@([A-Z0-9.-]+)\\.([A-Z]{2,4})'
regex = re.compile(pattern)
regex.findall(strings)
#如果使用match
m=regex.match(string)
m.groups()
#效果是这样的
suzyu123@163.com --> [(suzyu123, 163, com)]
#获取 list-tuple 其中的某一列
matches.get(i)
分组聚合,计算
group_by技术
# 根据多个索引分组,然后计算均值
means = df['data1'].groupby([df['index1'],df['index2']).mean()
# 展开成透视表格式
means.unstack()
分组后价将片段做成一个字典
pieces = dict(list(df.groupby('index1')))
pieces['b']
groupby默认是对列(axis=0)分组,也可以在行(axis=1)上分组
语法糖,groupby的快捷函数
df.groupby('index1')['col_names']
df.groupby('index1')[['col_names']]
#是下面代码的语法糖
df['col_names'].groupby(df['index1'])
df.groupby(['index1','index2'])['col_names'].mean()
通过字典或series进行分组
people = DataFrame(np.random.randn(5, 5),
columns=['a', 'b', 'c', 'd', 'e'],
index=['Joe', 'Steve', 'Wes', 'Jim','Travis'])
# 选择部分设为na
people.ix[2:3,['b','c']]=np.na
mapping = {'a': 'red', 'b': 'red', 'c': 'blue',
'd': 'blue', 'e': 'red', 'f' : 'orange'}
people.groupby(mapping,axis=1).sum()
通过函数进行分组
#根据索引的长度进行分组
people.groupby(len).sum()
数据聚合
使用自定义函数
## 对所有的数据列使用自定义函数
df.groupby('index1').agg(myfunc)
#使用系统函数
df.groupby('index1')['data1']describe()
根据列分组应用多个函数
#分组
grouped = df.groupby(['col1','col2'])
#选择多列,对每一列应用多个函数
grouped['data1','data2'...].agg(['mean','std','myfunc'])
对不同列使用不同的函数
grouped = df.groupby(['col1','col2'])
#传入一个字典,对不同的列使用不同的函数
#不同的列可以应用不同数量的函数
grouped.agg({'data1':['min','max','mean','std'],
'data2':'sum'})
分组计算后重命名列名
grouped = df.groupby(['col1','col2'])
grouped.agg({'data1':[('min','max','mean','std'),('d_min','d_max','d_mean','d_std')],
'data2':'sum'})
返回的聚合数据不要索引
df.groupby(['sex','smoker'], as_index=False).mean()
分组计算结果添加前缀
#对计算后的列名添加前缀
df.groupby('index1').mean().add_prefix('mean_')
将分组计算后的值替换到原数据框
#将函数应用到各分组,再将分组计算的结果代换原数据框的值
#也可以使用自定义函数
df.groupby(['index1','index2'...]).transform(np.mean)
更一般化的apply函数
df.groupby(['col1','col2'...]).apply(myfunc)
df.groupby(['col1','col2'...]).apply(['min','max','mean','std'])
禁用分组键
分组键会跟原始对象的索引共同构成结果对象中的层次化索引
df.groupby('smoker', group_keys=False).apply(mean)
分组索引转成df的列
某些情况下,groupby的as_index=False参数并没有什么用,得到的还是一个series,这种情况一般是尽管分组了,但是计算需要涉及几列,最后得到的还是series,series的index是层次化索引。这里将series转成dataframe,series的层次化索引转成dataframe的列。
def fmean(df):
"""需要用两列才能计算最后的结果"""
skus=len(df['sku'].unique())
sums=df['salecount'].sum()
return sums/skus
#尽管禁用分组键,得到的还是series
salemean=data.groupby(by=['season','syear','smonth'],as_index=False).apply(fmean)
# 将series转成dataframe,顺便设置索引
sub_df = pd.DataFrame(salemean.index.tolist(),columns=salemean.index.names,index=salemean.index)
# 将groupby的结果和sub_df合并
sub_df['salemean']=salemean
桶分析与分位数
对数据切分段,然后对每一分段应用函数
frame = DataFrame({'col1':np.random.randn(1000),
'col2':np.random.randn(1000)})
#数据分段,创建分段用的因子
#返回每一元素是属于哪一分割区间
factor = pd.cut(frame.col1, 4)
#分组计算,然后转成数据框形式
grouped = frame.col2.groupby(factor)
grouped.apply(myfunc).unstack()
用分组的均值填充缺失值
#自定义函数
fill_mean= lambda x:x.fillna(x.mean())
#分组填充
df.groupby(group_key).apply(fill_mean)
分组后不同的数据替换不同的值
#定义字典
fill_value = {'east':0.5, 'west':-1}
#定义函数
fill_func = lambda x:x.fillna(fill_value(x.name))
#分组填充
df.groupby(['index1','index2'...]).apply(fill_func)
sql操作
有时候觉得pandas很方便,但是有时候却很麻烦,不如SQL方便。因此pandas中也有一些例子,用pandas实现SQL的功能,简单的就不说了,下面说些复杂点的操作。
之所以说这个复杂的语句,是因为不想将这些数据操作分写在不同的语句中,而是从头到尾连续编码实现一个功能。
SQL复杂操作用到的主要函数是assign,简单说其实和join的功能是一样的,根据df1,df2的索引值来将df2拼接到df1上。
两个函数是query,也听方便的。
# 有一批销量数据,筛选出那些有2个月以上的销量产品的数据,说白了就是剔除那些新上市产品的数据
# 方法是先统计每个产品的数据量,然后选出那些数据量>2的产品,再在数据表中选择这些产品
# sku smonth
# a 1
# a 2
# a 3
# a 4
# b 5
# b 6
# b 7
# b 8
# c 9
# c 10
# 按sku分组,统计smonth的次数,拼接到salecount中,然后查询cnt>2的
salecount.assign(cnt=salecount.groupby(['sku'])['smonth'].count()).query('cnt>2')