pandas 常用操作

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)


版权声明:本文为w55100原创文章,遵循CC 4.0 BY-SA版权协议,转载请附上原文出处链接和本声明。