update:190705
访问具体位置
某列。
aaa = df['a'] = df.a = df.get('a')
某行
df[0:100],返回前100行。 直接括号[]里面写条件,优先级似乎是先匹配列,再匹配行。容易出问题,不推荐这种写法。
等价于 df.ix[0:100] 。
不等价于 df.loc[0:100]。 使用loc会包含第101行,即index=100的行,即最后一行,总共返回0~100,计101行。
df.irow(i) ,i行,不常用,不推荐。
df.iloc[i] , i行,返回series。
df.iloc[i:j],(i,j-1)行,返回dataframe。
某行某列
按offset df.iat[0,0],第一行第一列 #iat是特化的单元素访问方法,如果只需要对单元素进行取值、赋值的操作,用起来就很舒服。
df.loc[0,[0]]
df.ix[0,[0]]
按name df.loc['hello',['world']] ,'hello'行,'world'列。 特色就是目标列需要用一个list来装。
df.at[0,'world'],at理论上只支持name访问单个元素。但是由于我们日常使用时,不定义行的index时,默认的行name其实就是012345这些数字。
#补充一下最常用的loc
a=df.loc[0,['cre_time']]
b=df.loc[0,'cre_time']
print(type(a))
print(type(b))
#输出
<class 'pandas.core.series.Series'>
<class 'pandas._libs.tslibs.timestamps.Timestamp'>
#可以看出,loc在访问单列时,若不加括号,效果等同于.at
多行多列
使用ix,对列同时支持name和offset。
N行N列 df.ix[0:3,[0,1,2]] 前3行和前3列。
N行N列 df.ix['one':'two',[0,1] 使用index的name代替offset。
N行1列 df.ix[[1, 2], [0]]
1行N列 df.ix[0,[2,3,4]]
列比较蠢,需要多一个括号来装,还不支持冒号slice语法。
使用loc,对列只能使用列name。
N行N列 df.loc[0:3,['hello','world']]
使用iloc,对列只能使用offset。
N行N列 df.iloc[0:3,[0,1,2]]
总结:
at与iat只能取单个。
ix与loc俩兄弟可以取多行,多行包括单行。
loc与iloc,这俩兄弟取多行时,会返回最后一行。而ix不会,直接使用[]也不会。
at与iat的关系,同loc与iloc。不加i表示只支持用name,加了i表示只能用offset(或者说以相对位置作为index)。
取行,支持 【单行,枚举多行,slice】 3种操作。
取列,仅支持【单列,枚举多列】2种操作。
无论什么函数,都不允许对列进行slice操作。
注意1:
用多行多列的函数访问单个元素时,容易各种报错。
举例,df.iloc[0,[0]],访问某个单元格,但是返回是dataframe或者series类型的,某些对单个元素才合法的操作(e.g.赋值),此时不合法。
因此,访问某个单个元素时,使用 df.at 与 df.iat 是好习惯。
#报错x:df.loc[0,['happy']] = 'hello'
#推荐√:df.iat[0,0] = 'hello'
#不推荐√: df.loc[0,'happy'] = 'hello'
哭着回来提醒一下,iat的处理速度不知道比loc高到哪里去了。
处理1200w行的数据,用loc告诉我需要100小时,iat变成9分钟,这差距。
注意2:
用name访问单行/单列时,若索引指向的行/列不存在,则新建一个。
e.g. 我们常用 df['aa']=[1,2,3],本质上就是df['aa']指向了一个不存在的列,则新建之,并用传入的值来初始化。
很实用的特性。详见下文“逐行逐列添加数据”。
去重
先查重
df.duplicated()
返回一系列的bool值,表示第i行是否为重复行。
#想按每列去重,好像可以设置axis,待证。
确认有重复值存在了,再删除重复项
#全部列进行比较,全部列的值都相同的2行才判定重复
df.drop_duplicates()
#只对指定列进行比较
df.drop_duplicates(['happy'])
#默认的方向是自前向后,所以上数下第二行开始被判定为重复丢弃。
#可以手动更改方向,保留last one,删除former one
df.drop_duplicates(['happy'],keep='last',inplace=True)
#inplace生效时原地修改,默认为False
空缺值
判断存在
df.isnull().sum()
df.notnull()
填充0,或者丢弃
df['happy']=df['happy'].fillna(0)
df['happy']=df['happy'].dropna()
#也可以不指定列,直接丢弃全部含空缺值的行
df=df.dropna()
比较特殊一点的是,用另外一列的值去填na。
df['happy']=df['happy'].fillna(df['wow'])
修改列名
#暴力全修改,注意与现存的数量必须匹配
df.columns=['aa','bb','cc']
#rename传入字典,把cc列改为CC列
df = df.rename(columns={'cc':'CC'}) #有inplace参数
强制转为category类
df["Status"] = df["Status"].astype("category")
备注:虽然astype()函数有inplace参数,但是在我的环境内测试,即使inplace=True也无法成功更改dtype。
不知是否为bug。(pandas == 0.23.4,numpy==1.15.4)
出于这个原因,推荐使用 = 赋值的形式。
去空格
df['term']=df['term'].map(str.strip)
本质上是pd.Series.map()函数的一个特殊应用场景。
关于map请看 《pandas map与apply》
大小写
大写
df['term']=df['term'].map(str.upper)
小写
df['term']=df['term'].map(str.lower)
首字母大写
df['term']=df['term'].map(str.title)
同上,map的应用。
指定某列为索引
df_new = df.set_index('new_index_col')
df_new = df.set_index(['col1','col2'])
注意,这个操作可以用.reset_index(drop=False) 回溯。
完美回溯,无损回溯!
借用这个回溯功能,可以引出下一个非常实用的性能。
分组统计后设为新表
主要利用到.reset_index(drop=False)的能力。
g =df.groupby(['id','time'])
new_df = pd.DataFrame(g.size(),columns=['count'])
new_df = ad_up.reset_index(drop=False)
#得到一个新表
'id','time','count'
逐行逐列添加数据
用合法的语句访问单行/单列时,若索引指向的行/列不存在,则新建一个。
实例
#预定义列,希望一行一行的添加进来
df = pd.DataFrame(columns=['aa','bb'])
names= ['peter','david']
for i in range(2):
key = boys[i]
#此处我们用.loc访问index=key的一整行(省略列索引即可达到此效果)
df.loc[key] = [i,i+1]
#但由于index=key的这行不存在,所以会新建'peter'行,.etc
print(df)
打印结果
aa bb peter 0 1 david 1 2
可是日常需求中,一般人名需要单独作为一列。使用上面说的.reset_index()即可
df= df.reset_index(drop=False)
index aa bb 0 peter 0 1 1 david 1 2
改columns就很简单了。
预定义行,希望一列一列地加数据同理。
注意:
当df为空时,实际上第0行是不存在的,所以使用df.iloc[0] = [....]添加第0行,会报错。single position index is out of bound.
也就是说,这种方法只对使用name形式的访问生效,即df.loc[0]。 此时的0并非offset,而是某行的name。
同理,创建不存在的列 df['hello'] = [...],同样是以列名进行访问,所以能够生效。
分列
#想拆分df['target']这列
#保留原来的index需要专门指定index
new_df = pd.DataFrame(
[x.split('-') for x in df.target],
columns=['people','age'],index=df.index)
#按左表的行索引合并
df = pd.merge(df,new_df,left_index=True)
分级标记Cut
bins = [0, 5, 10, 15, 20]
level_name = ['A', 'B', 'C', 'D']
df['level'] = pd.cut(df['value'], bins, labels=level_name)
#依次对df['value']列进行比较
#如果落在左闭右开区间内[,)
#则匹配对应的level_name
注意,虽然在python里面字符串是可以比大小的,但是实测dataframe的某列若为字符串形式的,哪怕是字符串型的数字,应用pandas.cut函数也会报奇怪的错误。
如果是字符串数字,请自行处理,或者自定义一个cut。
pd.cut()返回的series,类型是category,枚举型。
删除行列
删除行,drop()
data = data.drop([3,4])
data.drop([3,4],inplace=True)
删除列,del(),pop()
del data['age']
data
_ = data.pop('age') #pop后原df不保留该列
#也可以用drop删除列
df.drop(columns=['hello','world'], axis=1, inplace=True)
按列合并dataframe
result = pd.merge(df1, df2, on='uid', how='left/right/inner/outer’,shuffixes=('_x','_y')
tips:
如果2个表存在重复的列名,就会由shuffixes来为重复列名添加后缀,这样很麻烦。
我们需要手动删除一列,再对另一列改名。
其实有避免这种重复劳动的解决方案。
#排除重复列名,再进行merge
def single_merge(df1,df2,on='uid',how='left'):
s1 = set(df1.columns)
s2 = set(df2.columns)
s3 = s2-(s1&s2)
s3.add(on)
cols_to_use = list(s3)
res = pd.merge(df1,df2[cols_to_use],on=on,how=how)
return res
train_csv = single_merge(train_csv,listing_info,on='listing_id',how='left')
四则运算
series和dataframe都有
.add
.sub
.multiple
.div
注意1:
如果是add或者sub另外一个series,则要考虑行index对齐的问题,若两个series的index完全不一致,则全部nan。被这个坑了。
使用前可以将其中之一reset_index(drop=True),或者将被加/减数list()一下。
注意2:
跟numpy里面的四则运算的函数名字区别!
np.add(a1,a2)
np.sub(a1,a2)
np.multiply(a1,a2) ,注意numpy的乘法是multiply!
np.divide(a1,a2) #python3中的np.divide == np.true_divide,且不能用 a=np.array([1,2,3]) ,a.divide()会报不存在
还有一些np.abs()之类的隐藏坑,pandas里面是没有的貌似。
时间转换
时间戳:定义为1970年之后的秒数
import time,datetime
#1970年秒数转time结构体
time_struct = time.localtime(1462482700)
print(time_struct)
#time.struct_time(tm_year=2016, tm_mon=5, tm_mday=6, tm_hour=5, tm_min=11, tm_sec=40, tm_wday=4, tm_yday=127, tm_isdst=0)
#这是个time结构体,可以直接访问 .tm_year等属性获取对应值
#结构体转字符串
time_string = time.strftime("%Y-%m-%d %H:%M:%S", time_struct)
print(time_string)
#2016-05-06 05:11:40
#字符串转datetime对象
datetime_obj = datetime.datetime.strptime(timeDateStr,"%Y-%m-%d %H:%M:%S")
print(datetime_obj)
print(type(datetime_obj))
#2016-05-06 05:11:40
#<class 'datetime.datetime'>
#你别看打印出来跟string没什么区别,类型已经改了。
#datetime对象转time结构体
struct_time = datetime_obj.timetuple()
#time结构体转时间戳 (秒数)
time_stamp = time.mktime(struct_time)
#datetime对象转字符串
time_string = datetime.datetime.strftime(datetime_obj,"%Y-%m-%d %H:%M:%S")
#等价于
time_string = datetime_obj.strftime("%Y-%m-%d %H:%M:%S")
直接取数
#datetime对象
year = datetime_obj.year
month = datetime_obj.month
day = datetime_obj.day
hour = datetime_obj.hour
minute = datetime_obj.minute
second = datetime_obj.second
#time_struct同理,直接.访问属性即可。
补充:datetime对象相减会返回timedelta类的对象。
start_time ='20190305051140'
end_time ='20190308051145'
start_obj = datetime.datetime.strptime(start_time,"%Y%m%d%H%M%S")
end_obj = datetime.datetime.strptime(end_time,"%Y%m%d%H%M%S")
i_am_time_delta = end_obj - start_obj
print(i_am_time_delta)
# 3 day, 00:00:00
#timedelta比较特殊一点
#其内部只储存days,seconds,microseconds
days = i_am_time_delta.days
seconds = i_am_time_delta.seconds
#虽然timedelta打印出来是 3 day, 00:00:00 ,这样子的。
#但后面的时分秒是用seconds算出来的
#内部并不存在 .hour 和 .minute 属性。
#但在构造函数里面,我们却可以指定hours 和 minutes !
time_delta_1 = datetime.timedelta(days = 1) #set .days to 1
time_delta_2 = datetime.timedelta(hours = 0.5)
print(time_delta_2.seconds)
#半个小时,30分钟,1800秒。 打印出来刚好是1800
time_delta_3 = datetime.timedelta(hours = 1, minutes = 1 ,seconds = 1 )
print(time_delta_3.seconds)
#得到3600+60+1=3661秒
#所以说,如果想对一个datetime类对象,加个1天,就可以
time_obj = time_obj + datetime.timedelta(days = 1)
#返回的类型,还是datetime.datetime类对象
#减一天,加一个小时,以此类推
#本质上跟c里的时间处理差不多。《C++中日期处理总结》
#想看更多关于时间的api推荐这篇:http://www.cnblogs.com/lhj588/archive/2012/04/23/2466653.html
自带函数
import pandas as pd
pd.to_datetime()
#将形如xx的字符串
#2018-02-04 08:28:15
#转为datetime类
df = pd.DataFrame({'id':['aa','bb','cc'],
'time':['2018-02-04 08:28:15',
'2018-02-04 09:30:11',
'2018-02-04 10:31:23']})
df['datetime'] = pd.to_datetime(df['time'])
#df.dtypes
#
#得到datetime类之后,可以用.dt直接访问属性,拿到月日
df['month'] = df['datetime'].dt.month
df['mday'] = df['datetime'].dt.day
#此外,官方示例还提供了
#s.dt.hour
#s.dt.second
#s.dt.quarter
#s.dt.date #2018-02-04
读取csv文件时,自动解析日期
import pandas as pd
#将time这一列解析为datetime
df = pd.read_csv('mydata.csv',header=0,date_parser = 'time')
#多列
df = pd.read_csv('mydata.csv', parse_dates=['auditing_date', 'due_date', 'repay_date'])
小技巧:
由于数字运算比字符串运算更快捷。
所以用数字存储month和day,然后
month_day=month*100+day
这样得到的数字直接表示日期。比如1月5号=105, 12月31日=1231。
占用内存
import pandas as pd
memory = df.memory_usage().sum() / 1024 ** 3 #单位GB
print('Data occupies {} GB memory.'.format(memory))
进行groupby后求众数mode
pandas:对dataframe进行groupby后求众数mode
保存为csv
df.to_csv('a.csv',header=True)