Pandas库用来实现对CSV的快速处理,其在numpy之上提供了index和column机制。
类型系统
1 | df.dtypes |
得到
如果显示类型为object,则可能是混杂的类型
Series对象的相关性质
相关成员
1 | df = pd.DataFrame([[1, 2], [3, 4]], index=['aa', 'bb'], columns=['a', 'b']) |
- name
表示这个Series的名字。如果Series是从pd.Series
构建的,等于传入的name参数的值,默认为空。如果是从DataFrame得到的,对应的是column或者index的值。 - index
输入输出
输入
可以使用下面的方式实现条件加载,其中lambda chunk
读取chunk这个DataFrame,判断它最后一行的id值,如果小于MAXN就继续读下一个。
1 | chunks = pd.read_csv(xxx, sep=',', skiprows=0, chunksize = 200) |
【这个方案不行,chunk.index.get_indexer_for
只是返回局部的index,所以还得用一个全局的tot来标记】
当然,如果没有id这一列,而只要单纯的前maxn列,则可以
1 | chunks = itertools.takewhile(lambda chunk: (chunk.index.get_indexer_for(chunk.index) < maxn).any(), chunks) |
其中的chunk.index.get_indexer_for
可以换成
1 | df.index.get_loc(index_name) |
输出
1 | with pd.option_context('display.max_rows', None, 'display.max_columns', None): |
以Excel表形式输出
可以借助于pd.ExcelWriter
组件来将DataFrame输出成Excel表。
首先创建一个writer
1 | writer = pd.ExcelWriter(u"{}.xlsx".format(name), engine='xlsxwriter') |
然后导出到一个sheet中,注意sheet_name
的长度是有限制的
1 | df.to_excel(writer, sheet_name=sheet_name) |
我们可以通过writer
对已经导入的进行进一步修改。在下面的代码中,我们创建了一个图标chart_cmp
,这个图标中包含了predict
和real
两个系列的数据,分别位于B
列的第2到42行,和C
列的2到42行。
1 | workbook = writer.book |
接着,我们可以指定主要坐标轴和次要坐标轴。
1 | chart_cmp.set_y_axis({'min': 0.4, 'max': 0.6}) |
交互
原生数组和DataFrame转换
可以通过一个字典创建DataFrame,这时候传入的字典是按照列来组织的。
可以指定数据和列名(甚至index)创建DataFrame,这时候传入的数组是按照行来组织的。
1 | df = pd.DataFrame(data=[[1,2,3],[10,20,30]], columns=["a", "b", "c"]) |
ndarray和DataFrame转换
从DataFrame到ndarray可以用
1 | df.values |
调试
查看所有的列,注意直接输出d.columns
会返回一个Index
,不是简写的,所以很难看。
1 | d.columns.values |
不限制打印行数
1 | pd.set_option('display.max_rows', None) |
索引与编号
Axis
和numpy一样,Pandas的axis
表示受影响的是哪一列。
我们可以这样去记忆,假如一个三维的 Dataframe,我们求sum(axis = 1)
,那么必然是axis
这一列没了,压成了 scalar。
往回带入到二维的情况,axis = 0
时,相当于行被压没了,也就是每一列上的所有的行加起来,如下所示。
1 | 1,2],[3,4]]) df = pd.DataFrame([[ |
对于二维数组的concat,那么就是axis=1
受影响,也就是两个列拼在一起
1 | pd.concat([c1, c2], axis = 1)) |
编号
reset_index
1 | 'bird', 389.0), ('bird', 24.0), ('mammal', 80.5), ('mammal', np.nan)], df = pd.DataFrame([( |
选择
loc的标签选择
.loc
主要有几点用法。
首先可以接受一个Label,或者一个Label数组,或者一个Range。用来索引
DataFrame
中的某一行1
2
3
4
5
6
7
8import pandas as pd
import numpy as np
df = pd.DataFrame([[1, 2], [3, 4], [5,6]], index=['a', 'c', 'b'], columns=['a', 'b'])
df
a b
a 1 2
c 3 4
b 5 6使用
loc[]
,得到一个Series,表示选择的row。1
2
3"a"] df.loc[
a 1
b 2使用
loc[[]]
,得到一个DataFrame,表示所有选择的row。1
2
3
4
5
6
7"a"]] df.loc[[
a b
a 1 2
"a", "b"]] df.loc[[
a b
a 1 2
b 5 6可以选择从
'a'
到'c'
之间的行。1
2
3
4'a':'c'] df.loc[
a b
a 1 2
c 3 4可以同时传入两个维度
如果这里面有一个是Label,那么就会降一维,如果是一个List或者Range,那么维数就不变。
得到一个Series1
2
3
4
5'a':'b', 'a'] df.loc[
a 1
c 3
b 5
Name: a, dtype: int64得到一个DataFrame
1
2
3
4
5'b']] df.loc[:, [
b
a 2
c 4
b 6得到一个Series
1
2
3'a', ['b']] df.loc[
b 2
Name: a, dtype: int64我们可以传入一个Bool数组,作为Mask,表示需要哪些行和列
下面的代码中,我们选择的是第0/2行、第1列,由于我们是通过List来调用的,所以返回的是DataFrame。1
2
3
4True, False, True], [False, True]] df.loc[[
b
a 2
b 6我们也可以指定一个较短的数组,后面会默认是False。同样得到DataFrame。
1
2
3True, False], [False, True]] df.loc[[
b
a 2对Series的补充说明
此外,loc
也可以用在Series
上,这时候我们得到Scalar1
2s.loc['B'] # 得到一个numpy.float64
s['B'] # 得到一个numpy.float64
iloc
相比loc,iloc提供了通过从绝对位置开始,而不是根据label的索引方式。
中括号__getitem__
__getitem__
的作用和loc
有差别。我们进行下面的比较。
可以发现,当传入一个Label时,loc
默认这个Label是行,而__getitem__
默认是列
1 | 1, 2], [3, 4]], index=['a', 'b'], columns=['a', 'b']) df = pd.DataFrame([[ |
对于loc,返回的是a这一行
1 | "a"] df.loc[ |
对于__getitem__
,返回的是a这一列
1 | "a"] df[ |
当我们传入一个List of Labels的时候,情况也是相同的。
1 | "a"]] df.loc[[ |
条件选择
可以通过以下的方式进行条件选择,或者判断有多少项满足条件。
借助于Bool数组
一个简单的方法就是借助于Bool数组去Mask,这也是数据处理中常见到的用法。
1 | 'a'] == 1 df[ |
通过下面的方法可以计算DataFrame
某一列中满足某些条件的有多少个
1 | df = pd.DataFrame({"class":[1,1,1,2,2], "value":[1,2,3,4,5]}) |
借助于where
当我们需要计算Series
的某一行满足条件的有多少个时,可以使用where
来做。下面语句的意思是过滤df
里面大于1的值,如果有不满足条件的,那么就将它的值改为100
1 | df.where(df > 1, 100) |
特别地,where
还有简化版,下面两个函数分别将未匹配的和匹配的改为NaN。
1 | s = pd.Series(range(5)) |
因此我们可以用下面的语句来计算满足条件的个数
1 | s.where(condition).count() |
复杂条件选择
可以使用&
和|
等符号。
需要注意,Python中的in不再使用,需要替换成.isin
。例如判断group
的field
系列里面是否有值是在lst之中的。
1 | df['field'].isin(lst) |
当需要判断单个值的时候会简单点,例如判断在group
的field
系列里面是否存在0这个值。
1 | 0 in group['field'].values |
ix
得到一个Series
1 | df.ix[0] |
比较
在条件选择时涉及比较问题,我们分类讨论,设置
1 | df = pd.DataFrame([[1, 2], [3, 4]], index=['a', 'b'], columns=['a', 'b']) |
与None比较
在比较
1 | df == None |
会产生如下的错误,所以
1 | TypeError: Could not compare [None] with block values |
要使用
1 | df is None |
这也是在Python中常常需要注意的一点
使用Callable
1 | lambda df: df.a > 2, :] df.loc[ |
使用Apply/Transform
对于更复杂的情况,我们可以借助于DataFrame.apply
,它能接收一个自定义化的函数。具体参考下文。
增删改查
数组合并/连接
append和concate默认是纵向连接,也就是结果中column是不变的,但是row变多了。merge默认是横向连接,也就是column变多了,但row不变。
append
在使用concat/append进行数组连接时候,需要特别注意索引的问题。如果我们希望实现类似numpy一样的数组合并,那么最好对合并的每一项都进行.reset_index(drop=True)
。
下面的代码会报错TypeError: Can only append a Series if ignore_index=True or if the Series has a name
。
1 | df = pd.DataFrame({'A' : [1, 2, 3], 'B': [4, 5, 6]}, index = [1, 2, 3]) |
那加上name
可以么?可以发现,append是默认添加一行的,但我们的Series不带columns属性。所以尽管我们预期的是将7和8添加到66行的A和B列上,但实际上新的DataFrame多了两列。
1 | 7, 8], name=66) ser = pd.Series([ |
于是解决方案应运而生,重置一下Column就可以了。这个方法就是将矩阵转置,然后去重置Index,然后再转置回来。
1 | True).T df = df.T.reset_index(drop= |
事实上构造一个DataFrame
会更简单。但是由于DataFrame的构造函数在这里是以列为基础的,所以下面的语句写起来并不舒服。
1 | 'A': [7], 'B' : [8]}, index=[66]) delta_df = pd.DataFrame({ |
特别地,当我们添加的Series
长度和原来的DataFrame不同时,会自动进行扩展。
1 | 1, 3)).T.reset_index(drop=True).T df = pd.DataFrame(np.random.randn( |
concate
另一个用来做数据合并的是concat
,它相当于是append
的升级版。我们最好将concat
理解成关于Index的Join而不是简单的拼接。pd.concate
可以指定ignore_index
参数控制是否对concate之后的数组重新编号。
merge
join
增加
append
删除
drop
可以通过drop
来删除列,这样的删除不是inplace的。当然,我们也可以指定inplace
参数。
1 | df = pd.DataFrame([[1, 2], [3, 4], [5,6]], index=['a', 'c', 'b'], columns=['a', 'b']) |
注意上面的columns
不能省略,也不能直接通过指定axis=...
的方式省略,否则会出现下面的错误。
1 | KeyError: "['a'] not found in axis". |
同理,我们可以用下面的命令删除行
1 | df.drop(index=["a", "b"]) |
pop
这个函数是以Series为格式返回被pop的列,同时inplace地去掉被pop的列。这个函数可定制范围比较小,不能接受数组。
1 | 1, 2], [3, 4], [5,6]], index=['a', 'c', 'b'], columns=['a', 'b']) df = pd.DataFrame([[ |
遍历
可以使用iteritems
来遍历Series
1 | ser = pd.Series(np.random.randn(4), index = [2, 3, 4, 5]) |
下面的代码对DataFrame按列进行遍历
1 | df = pd.DataFrame(np.random.randn(2, 2), columns=['A', 'B'], index = [2, 3]) |
返回得到(column, Series)
组成的tuple。
1 | ('A', 2 0.637096 |
对GroupBy对象进行遍历的结果情形将在GroupBy中单独讨论。
替换
根据Pattern替换
可以使用to_replace
函数
查找替换
swap
可以借助于loc
来实现列与列之间的交换,如下
1 | df.loc[:, [col1, col2]] = df.loc[:, [col2, col1]].values |
Map系列操作
基于Apply
Demo设置
1 | df = pd.DataFrame([[1, 2], [3, 4]], index=['a', 'b'], columns=['a', 'b']) |
Apply的声明如下
1 | DataFrame.apply(self, func, axis=0, raw=False, result_type=None, args=(), **kwds)[source] |
其中:
raw
为True表示传入的是ndarray
,否则是pd.Series
result_type
通过Apply查找
DataFrame.apply
函数方法接收一个axis
,表示这个函数会对哪一个轴去apply。例如在下面的语句中,当axis
为1时,就是对列去apply。这时候就会遍历所有的行,对于每一行row
的所有列去应用lambda函数。
1 | def P(x): |
可以看到,每一次遍历是针对的一行中的所有的列。
1 | ('--->', a 1 |
如果我们需要找到所有的行中,对应row['a']
的值在[3, 4]
中的所有row
,那么可以使用下面的方法。
1 | lambda row: row['a'] in [3, 4], axis=1)] # 得到一个DataFrame df[df.apply( |
通过Apply变换
在找到之后,我们就可以单独对这些列进行变换,例如我们需要将每一列中所有大于等于2的class
的值设置为0,除了where
,还可以用下面的办法
1 | df = pd.DataFrame({"class":[1,1,1,2,2], "value":[1,2,3,4,5]}) |
上面的做法实际上是对每一个row去map,修改对应的column项目的值,那么能不能把column选出来进行apply呢?执行下面的函数
1 | def handle_item(item): |
我们发现输出如下,并且再打印df,也是和原来一样的,所以apply不是inplace的。
1 | 0 0 |
实现安全除法
我们可以基于apply实现安全除法,当除数是0时,设置默认值0.5
1 | df = pd.DataFrame({"a":[1,1], "b":[0,2]}) |
注意Series.apply
没有axis
参数,所以如果传入的话会报错
1 | TypeError: <lambda>() got an unexpected keyword argument 'axis' |
在Apply过程中获得全局信息
有的时候,我们需要知道回调函数中传入的row的的相对Index和绝对Index,这个如何来做呢?
Transform
Aggregate/Reduce系列操作
下面的方法可以求出某些列的值
1 | df = pd.DataFrame([[1,2,3],[10,20,30]], columns=["a", "b", "c"]) |
或者我们可以借助apply
1 | "a", "c"]].apply(lambda x: x.sum(), axis=1) df[[ |
groupby
在Pandas中,对整行和整列进行处理是简单的,但有时我们需要对局部的行或者列进行某些处理,而对另外的行或者列进行另外的处理。一个简单的思路是将需要分开处理的逻辑放到不同的group里面进行处理,除了filter
出来之外,另一种方法是groupby
。
相关参数
1 | df = pd.DataFrame([[1, 2], [2, 3], [1, 4], [1, 3]], index=['aa', 'bb', 'cc', 'dd'], columns=['a', 'b']) |
1 | DataFrame.groupby(self, by=None, axis=0, level=None, as_index=True, sort=True, group_keys=True, squeeze=False, observed=False, **kwargs) |
group_keys
as_index
默认为False。
groupby
的返回值是DataFrameGroupBy
或者SeriesGroupBy
,这两个对象都继承了GroupBy
。
(DataFrame/Series)GroupBy
相关字段
ngroups
获知到底有多少个group产生。1
2
3
4
5g.ngroups
2
g.groups
{(1L, 2L): Int64Index([0], dtype='int64'), (3L, 4L): Int64Index([1], dtype='int64')}
DataFrameGroupBy
遍历
groupby会返回一个GroupBy对象,我们可以遍历它,得到的每一项是(key, group)
这样的tuple:
key
表示用来groupby的key,如果选择一个key
来groupby,那么key
就是一个scalar,否则,key
就是一个tuple。group
是一个DataFrame。
1 | 1,2],[3,4]], columns=["a", "b"]) df = pd.DataFrame([[ |
遍历了g
,可以看出g
中仍然包含了a
这个column
1 | for i in g: |
还可以指定多个column
1 | "a", "b"]) g = df.groupby([ |
编号
组内编号
还有一个常见场景是对GroupBy之后的每个组内的元素进行编号。
可以通过GroupBy.cumcount
函数来实现。
1 | 'A': [1, 3, 5, 7, 9], 'B': [1, 1, 2, 2, 2]}) df = pd.DataFrame({ |
cumcount
可以看作是
1 | self.apply(lambda x: pd.Series(np.arange(len(x)), x.index)) |
组间编号
1 | g.ngroup() |
过滤
可以基于DataFrameGroupBy.filter进行过滤,但得到的就是一个DataFrame
,原来的GroupBy
语义会被去掉。
map
1 | def pt(x): |
结果如下所示,可以发现不包含 group key 了。
1 | <class 'pandas.core.frame.DataFrame'> |
聚合
1 | 'A': [1, 3, 5, 7, 9], 'B': [1, 1, 2, 2, 2]}) df = pd.DataFrame({ |
随机算法
取样
复杂数据转换
分桶
1 | 'A': [1, 3, 5, 7, 9]}) df = pd.DataFrame({ |
返回的 buckets 是个关于 Interval 的 Series。
1 | for interval in buckets: |
Interval 可以和 GroupBy 结合
1 | g = df.groupby(buckets) |
可以遍历
1 | for i in g: |
可以结合之前说的 ngroups 来实现分桶之后映射到组
1 | 'gid'] = df.groupby([buckets]).ngroup() df[ |
onehot
1 | 'A': [1, 3, 5, 7, 9], 'B': [1, 1, 2, 2, 2]}) df = pd.DataFrame({ |
Reference
- https://pandas.pydata.org/pandas-docs/stable/user_guide/indexing.html
- https://pandas.pydata.org/pandas-docs/stable/reference/groupby.html?highlight=dataframegroupby
- https://pandas.pydata.org/pandas-docs/stable/user_guide/merging.html
- https://pandas.pydata.org/pandas-docs/stable/reference/api/pandas.DataFrame.html
- https://pandas.pydata.org/pandas-docs/stable/reference/api/pandas.Series.where.html
- https://pandas.pydata.org/pandas-docs/stable/reference/api/pandas.DataFrame.apply.html