Python数据分析 | (17) pandas的基本功能

本篇博客中,我将介绍操作Series和DataFrame中的数据的基本手段。后续将更加深入地挖掘pandas在数据分析和处理方面的功能。这不是pandas库 的详尽文档,主要关注的是最重要的功能,那些不大常用的内容(也就是那 些更深奥的内容)就交给你自己去摸索吧。

目录

1. 重新索引

2. 丢弃指定轴上的项

3. 索引、选取和过滤

4. 整数索引 

5. 算术运算和数据对齐

6. 函数应用和映射

7. 排序和排名

8. 带有重复标签的轴索引


1. 重新索引

pandas对象的一个重要方法是reindex,其作用是创建一个新对象,它的数据 符合新的索引。

obj = pd.Series([4.5, 7.2, -5.3, 3.6], index=['d', 'b', 'a', 'c'])
obj

用该Series的reindex将会根据新索引进行重排。如果某个索引值当前不存 在,就引入缺失值:

obj2 = obj.reindex(['a', 'b', 'c', 'd', 'e'])
obj2

对于时间序列这样的有序数据,重新索引时可能需要做一些插值处理。method选项即可达到此目的,例如,使用ffill可以实现前向值填充:

obj3 = pd.Series(['blue', 'purple', 'yellow'], index=[0, 2, 4])
print(obj3)
print(obj3.reindex(range(6), method='ffill')) #返回重新索引后的对象

借助DataFrame,reindex可以修改(行)索引和列。只传递一个序列时,会 重新索引结果的行:

frame = pd.DataFrame(np.arange(9).reshape((3, 3)),
                     index=['a', 'c', 'd'],
                     columns=['Ohio', 'Texas', 'California'])
print(frame)
frame2 = frame.reindex(['a', 'b', 'c', 'd'])
print(frame2)

列可以用columns关键字重新索引:

states = ['Texas', 'Utah', 'California']
frame.reindex(columns=states)

下表列出了reindex函数的各参数及说明:

 

2. 丢弃指定轴上的项

丢弃某条轴上的一个或多个项很简单,只要有一个索引数组或列表即可。由 于需要执行一些数据整理和集合逻辑,所以drop方法返回的是一个在指定轴 上删除了指定值的新对象:

obj = pd.Series(np.arange(5.), index=['a', 'b', 'c', 'd', 'e'])
print(obj)
new_obj = obj.drop('c')
print(new_obj)
print(obj.drop(['d', 'c']))

对于DataFrame,可以删除任意轴上的索引值。为了演示,先新建一个DataFrame例子:

data = pd.DataFrame(np.arange(16).reshape((4, 4)),
                    index=['Ohio', 'Colorado', 'Utah', 'New York'],
                    columns=['one', 'two', 'three', 'four'])
print(data)

用标签序列调用drop会从行标签(axis 0)删除值:

print(data.drop(['Colorado', 'Ohio']))

通过传递axis=1或axis='columns'可以删除列的值:

print(data.drop('two', axis=1)) #axis=1/'columns' 删除某列
print("------------")
print(data.drop(['two', 'four'], axis='columns')) 

许多函数,如drop,会修改Series或DataFrame的大小或形状,可以就地修 改对象,不会返回新的对象:

print(obj)
obj.drop('c', inplace=True) #使用inplace=True 就地更改
print("-----------")
print(obj) 

小心使用inplace,它会销毁所有被删除的数据。

 

3. 索引、选取和过滤

Series索引(obj[...])的工作方式类似于NumPy数组的索引,只不过Series的索引值不只是整数。下面是几个例子:

obj = pd.Series(np.arange(4.), index=['a', 'b', 'c', 'd'])
print(obj)
print("------")
print(obj['b'])
print("------")
print(obj[1])
print("------")
print(obj[2:4])
print("------")
print(obj[['b', 'a', 'd']])
print("------")
print(obj[[1, 3]])
print("------")
print(obj[obj < 2])

利用标签的切片运算与普通的Python切片运算不同,其末端是包含的:

obj['b':'c']

用切片可以对Series的相应部分进行设置:

obj['b':'c'] = 5
obj

用一个值或序列对DataFrame进行索引其实就是获取一个或多个列:

data = pd.DataFrame(np.arange(16).reshape((4, 4)),
                    index=['Ohio', 'Colorado', 'Utah', 'New York'],
                    columns=['one', 'two', 'three', 'four'])
print(data)
print("-----")
print(data['two'])
print("-----")
print(data[['three', 'one']])

这种索引方式有几个特殊的情况。首先通过切片或布尔型数组选取数据:

print(data[:2])
print("-----")
print(data[data['three'] > 5])

选取行的语法data[:2]十分方便。向[ ]传递单一的元素或列表,就可选择列。

另一种用法是通过布尔型DataFrame(比如下面这个由标量比较运算得出的)进行索引:

print(data < 5)
data[data < 5] = 0
print("-----")
print(data)

这使得DataFrame的语法与NumPy二维数组的语法很像。

  • 用loc和iloc进行选取

对于DataFrame的行的标签索引,我引入了特殊的标签运算符loc和iloc。它 们可以让你用类似NumPy的标记,使用轴标签(loc)或整数索引(iloc), 从DataFrame选择行和列的子集。

作为一个初步示例,让我们通过标签选择一行和多列:

print(data)
data.loc['Colorado', ['two', 'three']]

然后用iloc和整数进行选取:

print(data)
print("-----")
print(data.iloc[2, [3, 0, 1]])
print("-----")
print(data.iloc[2])
print("-----")
print(data.iloc[[1, 2], [3, 0, 1]])

这两个索引函数也适用于一个标签或多个标签的切片:

print(data)
print("-----")
print(data.loc[:'Utah', 'two']) #标签索引 切片包含末尾
print("-----")
print(data.iloc[:, :3][data.three > 5])#整数索引 切片不包含末尾

所以,在pandas中,有多个方法可以选取和重新组合数据。对于DataFrame,下表进行了总结。后面会看到,还有更多的方法进行层级化索 引。

在一开始设计pandas时,我觉得用frame[:, col]选取列过于繁琐 (也容易出错),因为列的选择是非常常见的操作。我做了些取舍,将 花式索引的功能(标签和整数)放到了ix运算符中。在实践中,这会导 致许多边缘情况,数据的轴标签是整数,所以pandas团队决定创造loc和iloc运算符分别处理严格基于标签和整数的索引。ix运算符仍然可用,但并不推荐。

 

4. 整数索引 

处理整数索引的pandas对象常常难住新手,因为它与Python内置的列表和元 组的索引语法不同。例如,你可能不认为下面的代码会出错:

ser = pd.Series(np.arange(3.))
print(ser)
print(ser[-1]) #报错

这里,pandas可以勉强进行整数索引,但是会导致小bug。我们有包含0,1,2的索引,但是引入用户想要的东西(基于标签或位置的索引)很难:

另外,对于非整数索引,不会产生歧义:

ser2 = pd.Series(np.arange(3.), index=['a', 'b', 'c'])
ser2[-1]

为了进行统一,如果轴索引含有整数,数据选取总会使用标签。为了更准 确,请使用loc(标签)或iloc(整数):

ser = pd.Series(np.arange(3.))
print(ser)
print("-----")
print(ser[:1])
print("-----")
print(ser.loc[:1]) #标签索引 切片包含末尾
print("-----")
print(ser.iloc[:1]) #整数索引 不包含末尾

 

5. 算术运算和数据对齐

pandas最重要的一个功能是,它可以对不同索引的对象进行算术运算。在将 对象相加时,如果存在不同的索引对,则结果的索引就是该索引对的并集。 对于有数据库经验的用户,这就像在索引标签上进行自动外连接。看一个简 单的例子:

s1 = pd.Series([7.3, -2.5, 3.4, 1.5], index=['a', 'c', 'd', 'e'])
s2 = pd.Series([-2.1, 3.6, -1.5, 4, 3.1],
               index=['a', 'c', 'e', 'f', 'g'])
print(s1)
print("-----")
print(s2)
print("-----")
print(s1+s2)

自动的数据对齐操作在不重叠的索引处引入了NA值。缺失值会在算术运算过 程中传播。

对于DataFrame,对齐操作会同时发生在行和列上:

df1 = pd.DataFrame(np.arange(9.).reshape((3, 3)), columns=list('bcd'),
                   index=['Ohio', 'Texas', 'Colorado'])
df2 = pd.DataFrame(np.arange(12.).reshape((4, 3)), columns=list('bde'),
                   index=['Utah', 'Ohio', 'Texas', 'Oregon'])
print(df1)
print("-----")
print(df2)
print("-----")
print(df1+df2)

把它们相加后将会返回一个新的DataFrame,其索引和列为原来那两个DataFrame的并集:

因为'c'和'e'列均不在两个DataFrame对象中,在结果中以缺省值呈现。行也 是同样。

如果DataFrame对象相加,没有共用的列或行标签,结果都会是空:

df1 = pd.DataFrame({'A': [1, 2]})
df2 = pd.DataFrame({'B': [3, 4]})
print(df1)
print("-----")
print(df2)
print("-----")
print(df1 - df2)

  • 在算术方法中填充值

在对不同索引的对象进行算术运算时,你可能希望当一个对象中某个轴标签在另一个对象中找不到时填充一个特殊值(比如0):

df1 = pd.DataFrame(np.arange(12.).reshape((3, 4)),
                   columns=list('abcd'))
df2 = pd.DataFrame(np.arange(20.).reshape((4, 5)),
                   columns=list('abcde'))
df2.loc[1, 'b'] = np.nan
print(df1)
print("-----")
print(df2)

将它们相加时,没有重叠的位置就会产生NA值:

df1 + df2

使用df1的add方法,传入df2以及一个fill_value参数:

df1.add(df2, fill_value=0)

下表列出了Series和DataFrame的算术方法。它们每个都有一个副本,以字 母r开头,它会翻转参数。因此这两个语句是等价的:

print(df1)
print("-----")
print(1/df1)
print("-----")
print(df1.rdiv(1))

与此类似,在对Series或DataFrame重新索引时,也可以指定一个填充值:

print(df1)
print("-----")
print(df2)
print("-----")
df1.reindex(columns=df2.columns, fill_value=0)
  • DataFrame和Series之间的运算

跟不同维度的NumPy数组一样,DataFrame和Series之间算术运算也是有明 确规定的。先来看一个具有启发性的例子,计算一个二维数组与其某行之间 的差:

arr = np.arange(12.).reshape((3, 4))
print(arr)
print("-----")
print(arr[0])
print("-----")
arr - arr[0]

当我们从arr减去arr[0],每一行都会执行这个操作。这就叫做广播 (broadcasting),之前的博客对此进行详细讲解。DataFrame和Series之间的 运算差不多也是如此:

frame = pd.DataFrame(np.arange(12.).reshape((4, 3)),
                     columns=list('bde'),
                     index=['Utah', 'Ohio', 'Texas', 'Oregon'])
series = frame.iloc[0]
print(frame)
print("-----")
print(series)

默认情况下,DataFrame和Series之间的算术运算会将Series的索引匹配到DataFrame的列,然后沿着行一直向下广播:

frame - series

如果某个索引值在DataFrame的列或Series的索引中找不到,则参与运算的 两个对象就会被重新索引以形成并集:

series2 = pd.Series(range(3), index=['b', 'e', 'f'])
print(series2)
print("-----")
print(frame)
print("-----")
frame + series2

如果你希望匹配行且在列上广播,则必须使用算术运算方法。例如:

series3 = frame['d']
print(frame)
print("-----")
print(series3)
print("-----")
frame.sub(series3, axis='index')

传入的轴号就是希望匹配的轴。在本例中,我们的目的是匹配DataFrame的 行索引(axis='index' or axis=0)并进行广播。

 

6. 函数应用和映射

NumPy的ufuncs(元素级数组方法)也可用于操作pandas对象:

frame = pd.DataFrame(np.random.randn(4, 3), columns=list('bde'),
                     index=['Utah', 'Ohio', 'Texas', 'Oregon'])
print(frame)
print("-----")
np.abs(frame)

另一个常见的操作是,将函数应用到由各列或行所形成的一维数组上。DataFrame的apply方法即可实现此功能:

f = lambda x: x.max() - x.min() #默认axis=0 沿各行操作
frame.apply(f)

这里的函数f,计算了一个Series的最大值和最小值的差,在frame的每列都 执行了一次。结果是一个Series,使用frame的列作为索引。

如果传递axis='columns'到apply,这个函数会在每行执行:

frame.apply(f, axis='columns') #axis=1 沿各列操作

许多最为常见的数组统计功能都被实现成DataFrame的方法(如sum和mean),因此无需使用apply方法。自定义的函数使用apply方法。

传递到apply的函数不是必须返回一个标量,还可以返回由多个值组成的Series:

def f(x):
    return pd.Series([x.min(), x.max()], index=['min', 'max'])
frame.apply(f)

元素级的Python函数也是可以用的。假如你想得到frame中各个浮点值的格 式化字符串,使用applymap即可:

format = lambda x: '%.2f' % x
frame.applymap(format)

之所以叫做applymap,是因为Series有一个用于应用元素级函数的map方 法:

frame['e'].map(format)

7. 排序和排名

根据条件对数据集排序(sorting)也是一种重要的内置运算。要对行或列索 引进行排序(按字典顺序),可使用sort_index方法,它将返回一个已排序 的新对象:

obj = pd.Series(range(4), index=['d', 'a', 'b', 'c'])
print(obj)
obj.sort_index()

对于DataFrame,则可以根据任意一个轴上的索引进行排序:

frame = pd.DataFrame(np.arange(8).reshape((2, 4)),
                     index=['three', 'one'],
                     columns=['d', 'a', 'b', 'c'])
print(frame)
print("-----")
print(frame.sort_index())  #axis=0
print("-----")
frame.sort_index(axis=1)

数据默认是按升序排序的,但也可以降序排序:

frame.sort_index(axis=1, ascending=False)

若要按值对Series进行排序,可使用其sort_values方法:

obj = pd.Series([4, 7, -3, 2])
print(obj)
print("-----")
obj.sort_values()

在排序时,任何缺失值默认都会被放到Series的末尾:

obj = pd.Series([4, np.nan, 7, np.nan, -3, 2])
print(obj)
print("-----")
obj.sort_values()

当排序一个DataFrame时,你可能希望根据一个或多个列中的值进行排序。 将一个或多个列的名字传递给sort_values的by选项即可达到该目的:

frame = pd.DataFrame({'b': [4, 7, -3, 2], 'a': [0, 1, 0, 1]})
print(frame)
print("-----")
frame.sort_values(by='b')

要根据多个列进行排序,传入名称的列表即可:

frame.sort_values(by=['a', 'b']) #先按a排序 如果a相同 则按b

frame1 = frame.sort_values(by=['a', 'b']) #先按a排序 如果a相同 则按b
print(frame1)
frame1 = frame1.reset_index() #排序后索引会变 不连续 可以重置索引 frame1.reset_index(inplace=True)
print(frame1)

 

排名会从1开始一直到数组中有效数据的数量。接下来介绍Series和DataFrame的rank方法。默认情况下,rank是通过“为各组分配一个平均排 名”的方式破坏平级关系的:

obj = pd.Series([7, -5, 7, 4, 2, 0, 4])
print(obj)
print("-----")
obj.rank()#-5 0 2 4 4 7 7

也可以根据值在原数据中出现的顺序给出排名:

print(obj)
print("-----")
obj.rank(method='first')

这里,条目0和2没有使用平均排名6.5,它们被设成了6和7,因为数据中标 签0位于标签2的前面。

你也可以按降序进行排名:

print(obj)
print("-----")#7 7 4 4 2 0 -5
# Assign tie values the maximum rank in the group
obj.rank(ascending=False, method='max')

下表列出了所有用于破坏平级关系的method选项。DataFrame可以在行或 列上计算排名:

frame = pd.DataFrame({'b': [4.3, 7, -3, 2], 'a': [0, 1, 0, 1],
                      'c': [-2, 5, 8, -2.5]})
print(frame)
print("-------")
frame.rank(axis='columns') #axis=1

对原数据进行排序,看原数据在排好序的序列中是第几名。

8. 带有重复标签的轴索引

直到目前为止,我所介绍的所有范例都有着唯一的轴标签(索引值)。虽然 许多pandas函数(如reindex)都要求标签唯一,但这并不是强制性的。我们 来看看下面这个简单的带有重复索引值的Series:

obj = pd.Series(range(5), index=['a', 'a', 'b', 'b', 'c'])
obj

索引的is_unique属性可以告诉你它的值是否是唯一的:

obj.index.is_unique

对于带有重复值的索引,数据选取的行为将会有些不同。如果某个索引对应 多个值,则返回一个Series;而对应单个值的,则返回一个标量值:

print(obj['a'])
print(obj['c'])

这样会使代码变复杂,因为索引的输出类型会根据标签是否有重复发生变化。

对DataFrame的行进行索引时也是如此:

df = pd.DataFrame(np.random.randn(4, 3), index=['a', 'a', 'b', 'b'])
print(df)
df.loc['b']

 

 

 

  • 1
    点赞
  • 3
    收藏
    觉得还不错? 一键收藏
  • 0
    评论
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值