Pandas笔记

发布于 2020-03-11  669 次阅读



这里是我使用pandas中的笔记经验,快速一览可以参考kaggle的pandas-cheatsheetPandas_Cheat_Sheet, PandasPythonForDataScience。详细内容可以参考Python Data Science Handbook一书。更原滋原味的资料强烈推荐pandas的官方文档, 大多数问题都可以在这里获得解决。

Introduction

import pandas as pd,pandas是panel data analysis(面板数据分析)的缩写,其中主要的数据结构有datafame和series,而大部分处理的都是dataframe。Q:what is the difference between a pandas Series and a single-column Data frame?(df['Age'] and df[['Age']])

帮助

当发现你不熟悉的属性或方法时,试试
- help(pd.DataFrame.info) & help(iris_data.info)
- print(iris_data.head.__doc__)
- ?iris_data.shape

在某些场景下?iris_data.shape会失效,但其他两种保证可以,或者查询pandas的官方文档,最后google一下。

常用属性

数据类型里,属性常常是对象方法的可选参数。

index(0&observation)

index属性对应数据的行,也就是一个观测,通常在方法中用0来指代是在index上进行操作(row-wise)
index常用的方法有:
- .reset_index()
- .set_index()
- .reindex()
在很多其他方法中也有as_index的参数,例如.groupby(),.to_excel()等。

columns(1&features)

columns属性对应数据的列,也就是特征,通常在方法中用1来指代是在columns上进行操作(column-wise)

pandas中大多数方法的axis的默认值是0,特别注意axis=0、在index上进行操作意味着什么Ambiguity in Pandas Data frame “axis” definition 。推荐显式使用axis参数(df.drop(["name"], axis='columns')),正如python之禅中所说,"Explicit is better than implicit."

其他

shape,dtypes等

常用方法&描述统计&对森林有个概貌

.head().tail() * .head(3)==[:3]==.iloc[:3]*
.describe()
.info()

参数设置

pd.options.display.max_columns = 20 完整显示dataframe的20列

pd.set_option("display.max_columns", 500)
pd.set_option("display.max_rows", 500)
pd.options.display.max_rows = 6
pd.set_option("display.width", 1000)

IN&OUT

小数据集

  • 通过列表的列表创建
df = pd.DataFrame([[1, 2, 3], ['lei', 'yu', 'yi']], index=['rank', 'name'])
  • 通过array创建
df = pd.DataFrame(np.array([[1, 2, 3], ['lei', 'yu', 'yi'], [27, 30, 18]]),
                    index=['rank', 'name', 'age'])

注意通过列表的列表或者array创建数据框的时候,默认一行是一个观测即对应一个index,如果需要可以考虑.transpose

  • 推荐使用字典化或者zip()来创建数据框
df = pd.DataFrame({'rank': [1, 2, 3], 'age': [26, 29, 18], 'sex': ['male', 'female', 'female']}, index=['lei', 'yu', 'yi'], columns=['age', 'rank', 'sex'])
df.index.name = 'name'
name = ['lei', 'yu', 'yi']
age = [27, 30, 18]
data_dict = {'name': name, 'age': age}
df1 = pd.DataFrame(data_dict)
data_tuples = list(zip(name, age))
df2 = pd.DataFrame(data_tuples, columns=['name', 'age'])

从文件中读取

实际工作中很少会手动创建数据框,大多直接跟数据文件或者数据库交互

df1 = pd.read_csv('./data.csv', header=None, sep=',', encoding='UTF-8')
df2 = pd.read_excel('./data.xlsx', sheet_name='sheet1')

保存多个数据框到一个excel文件的多个sheet页

xlsx = pd.ExcelFile('file.xlsx')
df = pd.read_excel(xlsx, 'sheet1')
writer = pd.ExcelWriter('output.xlsx')
df1.to_excel(writer, sheetname1)
df2.to_excel(writer, sheetname2)
writer.save()

.to_excel()方法中有参数as_index可以选定保存的excel是否带有index索引

与数据库交互

python与数据库交互

from sqlalchemy import create_engine
import pandas as pd

# 与Sqlite链接
engine = create_engine(r'sqlite:////home/l00466849/testDB.db')
# sqlite:///relative/path/to/file.db
# sqlite:////absolute/path/to/file.db

# 与Sqlserver链接
engine = create_engine('mssql+pymssql://<user>:<password>@<ip>/<database>?')

# 数据读取
sql = '<SelectSQL>'
df = pd.read_sql(sql, engine)

# 数据导入
df.to_sql('<TableName>', engine, if_exists='append', index=False)

# 执行一句sql
conn = engine.connect()
conn.execute('<ExecSQL>')
conn.close()

数据的增删查改

df.age -- get the attribute

获取一列最方便的就是直接使用df.name,其意思是获取这个对象的name属性,但使用很局限。
- 只能操作一列(最常用的就是df.age来获取一列的值)
- 列名需存在 (df.oldcolumn = a 重新给旧列赋值;df.newcolumn = a不会新增一列,只是新增一个属性;单纯df.newcolumn来get值会报错)
- 列名不能与已有的关键字重名,当然本身给列命名应该避免与关键字重名就是df.index, df.column, df.rank
- 列名需是合法的attribute,例如不能包含空格,不能以.、*等开头或结尾,不能以数字开头等等

df[]

直接使用[]来获取子集,可以说是非pandas特定的操作,对列表,数组来说也是可行的;局限在于只能单独获取行或者列,同时不适用于获取任意多行的数据
- df['age'] -- 输入一个已有key来获取一列,结果是series;或者新增一列(enlargement)
- df[['age', 'name']] -- 输入一个列表key来获取任意多列(可以是一列),结果是dataframe;或者交换([]不会做alignment操作)、设置、新增多列的值
- df[1:5:1]or df['c':'a':-1] -- 输入slice(左闭右开,可越界),用来获取多行(理论上可以使用df[1:2]来获取单独一行,不过这就没有必要了),不能新增、可以更改(set), df[2:4] = df[3:1:-1].values不推荐使用这样来交换行的值,不加values会索引对齐(alignment)
- df[df.age==28] -- 输入布尔序列(series,array,list(第二种方式的例外)都可,长度必须匹配行数)来筛选数据,局限在于只能筛选行

df.loc[,]

.loc和后面的.iloc都是pandas化的操作,再不影响简洁的情况下推荐使用。.loc指label location,也就是说即使你输入的是integer,他也会把他当label来看
- df.loc['a'] -- 取一行,等同df.loc['a', :],可以更改(set), 也可以新增(enlargement)
- df.loc[:, 'age'] -- 取一列, 不能缺省df.loc['age'], 可以新增(enlargement)
- df.loc[['a', 'b'], ['name', 'age']] -- 取任意行任意列任意顺序,注意.loc和.iloc会做alignment操作,也就是说df.loc[:, ['A', 'B']] = df[['B', 'A']]并不会交换A列和B列的值,若要交换使用df.loc[:, ['A', 'B']] = df[['B', 'A']].values或者df[['B', 'A']] = df.loc[:, ['A', 'B']]
- df.loc['a':'c']/df.loc[:, 'name':'age'] -- slice取多行多列,特别注意,这里的slice是左闭右闭,且因为是label,所以label的排序会影响start和stop缺省的情况,详细查看
- df.loc[df.age==28, df.columns.isin(['name', 'age'])] --输入布尔序列(类似[]都可),和[]不同的是不用匹配长度(不过匹配长度是好习惯),短包容,长了若多出部分为False也包容(多出部分有True就会indexoutofbounded)

df.iloc[,]

.iloc指integer location,指绝对位置的integer。将.loc的label替换为integer类比就可以,需要注意以下几点:
- 0-indexed
- slice是正常的slice(左闭右开)
- .iloc可以更改数据,但不能新增(enlargement)df.iloc[:, 4]=3、行列都不行
- 输入的布尔不能是series,可以是array和list(可能是个未修复的issue)df.iloc[df.age>18], 用 df.iloc[(df.Key>2).values]
- .get_loc()可以获得label的位置

布尔查询

实际工作中,布尔查询使用的非常广泛
- ~,&,|:布尔操作符,计算优先级高,注意使用()
-

df[df.name=='lei'], df.loc[df.name=='lei']
df.set_index('name', inplace=-True), df.loc['lei']
df.set_index('name', inplace=True), df.sort_index(inplace=True), df.loc['lei']

速度逐渐变快,可以通过df.index.is_monotonic验证index是否有序
- df[~(df.age<20)]
- df.loc[df.age>18, df.columns.isin(['age', 'rank'])]
- df.iloc[(df.age>18).values, :]
- df.where() -- 不满足条件的默认置为空
- df.mask() -- 满组条件的默认置为空
- df.query() -- df.query('age>18 and rank<3')相比 df.loc[(df.age>18)&(df.rank<3),:]要通俗简洁

总结

  • 获取标量:df.at[,]/df.iat[,](.loc和.iloc的特例,获取标量数据速度更快),df.loc[,], df.iloc[,]
  • 获取一列:df.age, df['age'], df.loc[:, 'age'], df.iloc[:,]
  • 获取一行:df[5](integer location), df.loc['a'], df.iloc[5]
  • 获取多列:df[['name', 'age']], df.loc[:,['name', 'age']], df.iloc[:, [3, 5]]
  • 获取多行:df[1:5], df.loc['a':'e'], df.iloc[1:3], df.loc[['a', 'e']], df.iloc[[1,3,4]]
  • 获取多行多列:df.loc[[],[]], df.iloc[[],[]]
  • 注意df[['name']], df.loc[[2], :], df.iloc[1:2, :]查询的结果都为数据框
  • 新增列:df['newcolumn'] = a, df.loc[:, 'newcolumn'] = b
  • 新增行:df.loc['newrow',:] = c
  • 更改原数据的值时,注意SettingWithCopyWarning,不要使用chained assignment,习惯使用.copy(), 具体见下一节

view or copy

强烈推荐阅读官方说明,也可以参考这篇How to fix the settingwithcopywarning,还有翻译版
简易的总结如下:
1. 避免使用链式赋值: data['age']['yi'] = 17
包括隐式链式赋值也要避免:

data_female = data.loc[data.sex=='female', :]
data_female.loc['yi', 'age'] = 28

使用前者你会得到SettingWithCopyWarning警告,使用后者你可能会得到这个警告,也可能不会(更糟)。
2. 确认你是否需要一个新的副本,还是要在原始数据上操作,若是新的副本,请使用
data_less = data.loc[data.sex=='female', :].copy();
若是在原始数据上操作,使用.loc/.iloc在原数据上操作,速度更快,结果更准确。
data.loc['yi', 'age'] = 28

当你在获取数据(get)时,Pandas不能确定给你的是view还是copy(跟当时的内存有关),更改view即是更改原始数据的值,而更改copy则不会影响原数据。可以使用.is_copy()来初步判断获取的是view还是copy。.is_copy(futurewaring, 后面的版本会废弃) 若为None(非人为操作),就是一个copy(通常使用.copy()生成);强烈建议不要人为更改该属性为None,即使没有warning,你也可能是在玩火。若不为空,则并不确定是copy还是view(reference);强烈推荐使用.copy(),可以显式的重新赋值。判断何时生成view何时生成copy

操作是否有效--inplace/assignment

what rules does pandas use to generate a view or a copy?
1. 所有的操作都是copy,例如.append()(和list的append方法区分开)
2. 如果给定参数inplace=True,则会在原数据上操作;不是所有方法都有该参数
3. .loc/.iloc/.at/.iat 使用这些索引方法在做assignment时会在原数据上操作
4. 链式赋值时的中间结果可能是view,也可能是copy,需要手动确认
5. 部分方法特殊,如.insert()没有inplace参数而直接在原数据上操作;而另一些方法返回新的数据框而没有inplace参数,只能使用assignment操作,如.assign()。

其他常用操作数据框的方法

  • df.sort_values(by=['rank', 'age'], ascending=False, inplace=True) # 按指定列对数据进行排序
  • df.drop(['column_name'], axis='column', inplace=True) # 删除指定行或列
  • df.drop_duplicates(inplace=True) # 去掉重复行
  • item_data.rename(columns={'ROOT_PARENT_ITEM': 'ITEM'}, inplace=True) # 给列重命名
  • df.insert(2, column) # 在第3列插入column
  • df.assign()
  • df.dropna() # 去掉含有缺失值的行或列
    1. df.dropna(subset=['name', 'age'], how='all') # 删除name和age列都为空的行
    2. df.dropna(subset=['name', 'age'], thresh=1) # 功能同上
    3. data=df.loc[~((df['name'].isnull())&(df['age'].isnull()))] # 功能同上
  • .fillna() # 填充缺失值

  • df.nunique() # 返回每列不重复值的个数
  • .astype('str') # 更改列的类型
  • df.name.combine(name2, lambda x1, x2: x2 if x2 else x1) # name,name2对应x1, x2
  • df.name.combine_first(name2) # 类似SQL的nvl(name2, name), name2 if name2 else name1

按某一列的条件依据另一列生成一个新的列

把一个连续数值的列生成一个新的离散的数值列

groupby

groupby和SQL里的group by的功能非常类似,主要思路是将原数据框根据映射分成多个子数据框(split),然后执行某些操作(apply),最后各个组得到的结果合并(combine)输出最终结果。这一部分使用的df定义如下

arrays = [['bar', 'bar', 'baz', 'baz', 'foo', 'foo', 'qux', 'qux'],
              ['one', 'two', 'one', 'two', 'one', 'two', 'one', 'two']]
index = pd.MultiIndex.from_arrays(arrays, names=['first', 'second'])
df = pd.DataFrame({'A': [1, 1, 1, 1, 2, 2, 3, 3],
                              'B': np.arange(8)},  index=index)
df.reset_index(level=1, inplace=True)

分割数据

grouped = df.groupby(['A'],as_index=False,axis=0)
可以用axis指定按行分和按列分,常用的归类方式有:
- df.groupby(['A']) # 这是python的语法糖,也即df.groupby(df['A']),用'A'列来归类
- df.groupby(['A', 'B']) # 类比上例,可以用多列来归类
- df.groupby(level=0) # 指定level0的index来归类,可以与列组合使用
- df.groupby([1, 1, 1, 2, 2]) # 给定一个列表或者array或者series等,只要长度和归类维度的长度相同就行,比如df.groupby(df.sum(), axis=1)
归类得到的结果在没有执行下一步前实际是并没有分割重排的,大家还是在原来的位置,只是pandas给他们做了标记,groupby之后的对象可以理解为一个带有分组标记的数据框。在这个基础上常用的方法有:
- grouped.first() # 提取每个group的第一行
- grouped.head(1) # 也是提取每个group的第一行,与上例的区别在于.head()使用旧的index,.first()使用新的index
- grouped.last() # 提取每个group的最后一行
- grouped.nth(3) # 提取每个group的第三行(注意不是从0开始)
- grouped.get_group('a') # 提取'A'列值为a的组,注意和上述例子的区别
- grouped.size() # 查看每一组的行数
- grouped['C', 'D'] # 也可以执行取列操作
- for (name, group) in grouped: # 可以循环提取所有的组名和子数据框
- grouped.sum() # 会对所有可以执行sum的列都执行求和操作

执行操作:聚合、转换、过滤

执行完分组操作后,对每一个组,你就当一个数据框来看待就可以了,你能想到在数据框执行的操作大多都能在这些子数据框上执行,你只需要使用agg告诉grouped对子数据框要执行的统一操作

agg

agg是最常用的,对每组数据进行聚合能够提炼信息(获取统计量、关键指标、row-wise的reduce),常用的使用方法有以下几种:
- grouped.agg(np.sum) # 对子数据框执行.sum()操作后合并,注意执行sum的axis会优先兼容合并所需要的维度,再使用默认axis,思考 df.groupby(df.sum(), axis=1).sum() 和子数据框执行sum后合并结果(维度多半对不上)的差异
- grouped.agg([np.sum, np.mean]) # 可以对子数据框的每列执行多个聚合操作
- gropued.agg(max_value=('A', 'max'), sum_value=('A', 'sum')) # 更有针对性更方便,对每列执行多个操作,并可以重命名,这里的代码等同于grouped.agg(max_value=pd.NamedAgg(column='A', aggfunc='max'))
- grouped.agg({'A': max, 'B': sum}) # 对每一列执行给定操作,不能对一列执行多个操作,key也要对应已有的列名,同时实际中经常需要给之后结果的列重命名(.rename(columns={'oldname': 'newname'}))
其中常用的聚合函数有
- .min()/.max()
- .sum()
- .mean()
- .nuique() # 每一组每列不重复的个数
- .count() # 每一组每一列非空的个数
- .describe()
- .std()
- .sem()

transform

transform和agg不同的是transform不会改变原数据框聚合的维度,他会在每个子数据框的每一列都进行转换(agg操作的则是数据框,transform操作的是列),常用于在原数据框上新增一列
grouped.transform(lambda x: x.max()-x.min())
举个例子,以下的三种操作方式得到的效果相同
- df['C'] = df.groupby('A')['B'].transform('sum')
- df['D'] = df['A'].map(df.groupby('A')['B'].sum().to_dict())
- df = pd.merge(df, df.groupby('A')['B']).sum().rename('E'), how='left', on='A')
可以看到后两种方式都不够直接,python里达到同一个目的通常都有多种方式,推荐使用最直接明了的。

filter

filter可以理解为SQL中group by后的having,用来筛选不满足条件的组,filter执行操作的也是子数据框,和agg一样。grouped.get_group('a')就是只要满足这个名字为a的组,也是一种过滤,但和grouped.head(1), grouped.tail(1),grouped.first()返回子集还是有一点区别。举例子说明:
grouped.filter(lambda x: len(x)>2)

apply

前面三种操作各司其职,而apply可以说是万金油了。apply可以对每个数据框执行任何你想操作的事情,只要最后每组得到结果能够在维度上合并起来就行。例如
- df.groupby('second')['A', 'B'].apply(lambda x: 1)
- df.groupby('second')['A', 'B'].apply(lambda x: pd.series([0, 1]))
- df.groupby('second')['A', 'B'].apply(lambda x: x.copy())
这些都不太有实际作用,只是举个例子。理论上是可以实现上述三种操作的任意功能,不过在效率上会低一点,代码也没那么直接,但是能实现的功能肯定是更加强大的。举几个实际例子:
- df.groupby('second')['A', 'B'].apply(lambda x: x.sort_values('B', ascending=False).head(2)) # 获取每一组中B值前二的行
-

df.groupby(level=0)['second', 'A'].apply(
    lambda x: ' ;'.join(x.second+' * '+x.A.astype(str)))
# 可以用于获取每组的配置

concat&merge

concat和merge的功能各有偏重,在某些情况下可以实现同样的需求(这很正常),注意区分他们的使用范围。

concat

concat用于合并数据,个人理解为原本属于一张表的信息,只能按index 合并列、或者行合并(类比SQL的union all),pd.concat([df1, df2])
同时提及append操作(简化快捷版的concat),df1.append(df2),在多个子数据框合并为一个大数据框时,考虑append返回的是copy,使用concat会更加高效

merge

merge用于拼表(类比SQL的join),将不同表的信息整合到一张宽表上来
pd.merge(left, right, how='left', on='A')
pd.merge(left, right, how='left', left_index=True, right_on='A') # 左右指定拼的列或index灵活性大
同时提及join操作(简化快捷版merge), df1.join(df2)

pivot 数据透视

pivot可以类比理解Excel的透视表,个人理解为groupby(理论上groupby都可以实现)的特例简化快捷版操作,对生成交叉列联表,透视比较有效,不过功能也有限。其中有三个参数,index指定行,columns指定列,values指定要显示的值的列。举个例子
- df.pivot(columns='second', values='A') # df为groupby小节中的df


持续更新...