9.4.索引对象
from toolkit import H
import numpy as np
import pandas as pd
import pprint
Windows 10
Python 3.8.8 @ MSC v.1928 64 bit (AMD64)
Latest build date 2021.03.02
pandas version: 1.2.2
numpy version: 1.20.1
索引对象
pandas的索引对象负责管理轴标签和其他元数据(比如轴名称等)。构建Series或DataFrame时,所用到的任何数组或其他序列的标签都会被转换成一个Index,而 index对象是不可变的 ,也就是index对象的元素不可修改(immutable)。如果要修改index内容,只能通过重新赋值的形式。
不可修改性非常重要,因为这样才能使Index对象在多个数据结构之间安全共享。
下表列出了pandas中主要的Index对象:
类 | 说明 |
---|---|
Index | 最泛化的Index对象,将轴标签表示为一个由Python对象组成的NumPy 数组 |
Int64lndex | 针对整数的特殊Index |
Multiindex | “层次化”索引对象,表示单个轴上的多层索引。可以看做由元组组成的数组 |
Datetimelndex | 存储纳秒级时间截(用NumPy的datetime64类型表示) |
Periodlndex | 针对Period数据(时间间隔)的特殊Index |
以下是pandas中所有Index对象:
['CategoricalIndex',
'DatetimeIndex',
'Float64Index',
'Index',
'Int64Index',
'IntervalIndex',
'MultiIndex',
'PeriodIndex',
'RangeIndex',
'TimedeltaIndex',
'UInt64Index']
每个索引都有一些方法和属性,它们可用于设置逻辑并回答有关该索引所包含的数据的常见问题。下表列出了这些函数:
说明 | 方法 |
---|---|
append | 连接另一个index对象.产生一个新的Index |
diff | 计算差集,并得到一个Index |
intersection | 计算交集 |
union | 计算并集 |
isin | 计算一个指示各值是否都包含在参数集合中的布尔型数组 |
delete | 删除索引i处的元素,并得到新的Index |
drop | 删除传入的值.并得到新的Index |
insert | 将元素插入到索引i处,并得到新的Index |
is_monotonic | 当各元素均大于等于前一个元素时,返回TRUE |
is_unique | 当Index没有重复值时,返回True |
unique | 计算Index中唯一值的数组 |
Index
pd.Index(data=None, dtype=None, copy=False, name=None, tupleize_cols=True,
kwargs)
参数:
data
:一个array-like
,必须是一维的name
:一个字符串,为Index
的名字。dtype
:指定数据类型。如果为None
,则默认为object
copy
:一个布尔值。如果为True
,则拷贝输入数据data
tupleize_cols
:一个布尔值,如果可能则尽量创建MultiIndex
对象
pd.Index(['a', 'b', 'c'])
Index(['a', 'b', 'c'], dtype='object')
实际上,通过pd.Index
类也可以创建pd.Int64Index
、pd.Float64Index
、pd.RangeIndex
、pd.UInt64Index
、pd.DatetimeIndex
、pd.TimedeltaIndex
from datetime import datetime, timedelta
print(type(pd.Index([1,2,3,4], dtype=np.uint64, name='UInt64Index')))
print(type(pd.Index([1,2,3,4], dtype=np.int64, name='Int64Index')))
print(type(pd.Index([1,2,3,4], dtype=np.float64, name='Float64Index')))
print(type(pd.Index(range(5), name='RangeIndex')))
print(type(pd.Index([datetime.today()], name='DatetimeIndex')))
print(type(pd.Index([timedelta(microseconds=1)], name='TimedeltaIndex')))
<class 'pandas.core.indexes.numeric.UInt64Index'>
<class 'pandas.core.indexes.numeric.Int64Index'>
<class 'pandas.core.indexes.numeric.Float64Index'>
<class 'pandas.core.indexes.range.RangeIndex'>
<class 'pandas.core.indexes.datetimes.DatetimeIndex'>
<class 'pandas.core.indexes.timedeltas.TimedeltaIndex'>
多级索引 MultiIndex
MultiIndex
代表的是多级索引对象。它继承自Index
,其中的多级label
采用元组对象来表示。因此,MultiIndex
可以看作一个元组数组。在MultiIndex
内部,并不直接保存元组对象,而是使用多个Index
对象保存索引中每级的label
。
pd.MultiIndex(levels=None, codes=None, sortorder=None, names=None,
dtype=None, copy=False, name=None,
verify_integrity=True)
参数:
levels
:一个数组的列表,指定每一级的索引。codes
:整数列表的列表,指定每个位置对应的索引标签。sortorder
:一个整数,给出了已经排序好了的level
级别。names
:一个字符串序列,指定每级索引的name
。copy
:一个布尔值。如果为True
,则拷贝基础数据。verify_integrity
:一个布尔值。如果为True
,则检测各级别的label/level
都是连续的且有效的。name
:MultiIndex
的名字。
# index1 和 index2 是等价的
index1 = pd.Index([('A', 'x1'), ('A', 'x2'), ('B', 'y1'),
('B', 'y2'), ('B', 'y3')], names=['class1', 'class2'])
index2 = pd.MultiIndex(levels=[['A', 'B'], ['x1', 'x2', 'y1', 'y2', 'y3']],
codes=[[0, 0, 1, 1, 1], [0, 1, 2, 3, 4]],
names=['class1', 'class2'])
pd.DataFrame(np.random.randint(1, 10, (5, 3)), index=index2)
0 1 2
class1 class2
A x1 8 1 7
x2 7 2 1
B y1 4 7 8
y2 2 6 5
y3 2 8 3
一般使用如下替代构造方法来创建MultiIndex
:
MultiIndex.from_arrays(arrays, sortorder, names)
:将list of array-likes
转换为MultiIndex
。每个array-like
对象给出对应层级的所有数据点的索引。MultiIndex.from_tuples(tuples, sortorder, names)
:将list of tuple-likes
转换为MultiIndex
。每个tuple-likes
都是行/列索引。MultiIndex.from_product(iterables, sortorder, names)
:将list of iterables
转换为MultiIndex
。各个iterable对象按顺序指明了不同层级的索引标签,每个数据点的索引根据笛卡尔积算法生成。MultiIndex.from_frame(df, sortorder, names)
:将DataFrame
转换为MultiIndex
。DataFrame
的每一列给出了对应层级的所有数据点的索引。
也可以通过传递一个元组列表给Index()
,并且将tupleize_cols
设置为True
来创建MultiIndex
from_tuples
arrays = [
['bar', 'bar', 'baz', 'baz', 'foo', 'foo', 'qux', 'qux'],
['one', 'two', 'one', 'two', 'one', 'two', 'one', 'two']]
tuples = list(zip(*arrays))
pprint.pprint(tuples)
print('')
index = pd.MultiIndex.from_tuples(tuples, names=['first', 'second'])
pprint.pprint(index)
[('bar', 'one'),
('bar', 'two'),
('baz', 'one'),
('baz', 'two'),
('foo', 'one'),
('foo', 'two'),
('qux', 'one'),
('qux', 'two')]
MultiIndex([('bar', 'one'),
('bar', 'two'),
('baz', 'one'),
('baz', 'two'),
('foo', 'one'),
('foo', 'two'),
('qux', 'one'),
('qux', 'two')],
names=['first', 'second'])
from_product
iterables = [['bar', 'baz', 'foo', 'qux'], ['one', 'two']]
pprint.pprint(iterables)
print('')
index = pd.MultiIndex.from_product(iterables, names=['first', 'second'])
pprint.pprint(index)
[['bar', 'baz', 'foo', 'qux'], ['one', 'two']]
MultiIndex([('bar', 'one'),
('bar', 'two'),
('baz', 'one'),
('baz', 'two'),
('foo', 'one'),
('foo', 'two'),
('qux', 'one'),
('qux', 'two')],
names=['first', 'second'])
from_frame
df = pd.DataFrame([['bar', 'one'], ['bar', 'two'],
['foo', 'one'], ['foo', 'two']],
columns=['first', 'second'])
pprint.pprint(df)
print('')
index = pd.MultiIndex.from_frame(df)
pprint.pprint(index)
first second
0 bar one
1 bar two
2 foo one
3 foo two
MultiIndex([('bar', 'one'),
('bar', 'two'),
('foo', 'one'),
('foo', 'two')],
names=['first', 'second'])
从list-like创建MultiIndex
arrays = [
np.array(['bar', 'bar', 'baz', 'baz', 'foo', 'foo', 'qux', 'qux']),
np.array(['one', 'two', 'one', 'two', 'one', 'two', 'one', 'two'])]
pprint.pprint(arrays)
pd.DataFrame(np.random.randn(8, 4), index=arrays)
[array(['bar', 'bar', 'baz', 'baz', 'foo', 'foo', 'qux', 'qux'],
dtype='<U3'),
array(['one', 'two', 'one', 'two', 'one', 'two', 'one', 'two'],
dtype='<U3')]
0 1 2 3
bar one 1.438074 0.851745 -0.263231 0.285984
two 0.830161 -1.887687 0.175476 1.057429
baz one -1.066494 -0.282858 0.760714 0.069183
two 0.333587 -0.724593 -0.215265 -1.637538
foo one 0.050430 -1.658196 -0.409160 0.692135
two 0.397801 -0.158865 1.922566 0.529642
qux one -1.022481 0.214911 0.513812 0.727262
two -1.325092 -2.795077 0.403388 -1.136477
# pd.Series(np.random.randn(8), index=arrays)
选择数据
列索引是多级索引
创建示例dataframe:
tuples = [('bar', 'one'),
('bar', 'two'),
('baz', 'one'),
('baz', 'two'),
('foo', 'one'),
('foo', 'two'),
('qux', 'one'),
('qux', 'two')]
index = pd.MultiIndex.from_tuples(tuples, names=['first', 'second'])
df = pd.DataFrame(np.random.randn(3, 8), index=['A', 'B', 'C'], columns=index)
df
first bar baz ... foo qux
second one two one ... two one two
A -0.936493 1.624851 0.381365 ... 0.510066 -0.079910 -0.659817
B -1.170663 -0.427186 1.827135 ... -0.489893 0.119415 0.853525
C 0.358102 -1.269896 0.434656 ... -0.628121 0.234735 -0.563489
[3 rows x 8 columns]
dataframe的[]
选择列:
df['bar']
second one two
A -0.936493 1.624851
B -1.170663 -0.427186
C 0.358102 -1.269896
[]
使用多级索引:
# 实际上与 df[('bar', 'one')] 等价
df['bar', 'one']
A -0.936493
B -1.170663
C 0.358102
Name: (bar, one), dtype: float64
[[]]
选择多列:
df[['bar', 'baz', 'foo']] # list
first bar baz foo
second one two one two one two
A -0.936493 1.624851 0.381365 -2.307657 0.144425 0.510066
B -1.170663 -0.427186 1.827135 -0.303377 0.037561 -0.489893
C 0.358102 -1.269896 0.434656 -1.654805 -0.907289 -0.628121
()
用于同一轴的多级索引:
df[[('bar', 'one'), ('baz','one')]] # list of tuples
first bar baz
second one one
A -0.936493 0.381365
B -1.170663 1.827135
C 0.358102 0.434656
# 行列索引
print(df.loc['A', 'bar'], '\n')
# () 用于同一轴的多级索引
print(df.loc['A', ('bar', 'one')], '\n')
# [] 用于增加轴 以下三行代码等价
print(df.loc['A', [('bar', 'one')]], '\n')
print(df.loc['A', ('bar', ['one'])], '\n')
print(df.loc['A', (['bar'], 'one')], '\n')
# [] 用于增加轴
print(df.loc['A', ['bar']], '\n')
second
one -0.936493
two 1.624851
Name: A, dtype: float64
-0.9364927994329391
first second
bar one -0.936493
Name: A, dtype: float64
first second
bar one -0.936493
Name: A, dtype: float64
first second
bar one -0.936493
Name: A, dtype: float64
first second
bar one -0.936493
two 1.624851
Name: A, dtype: float64
# tuple of lists
print(df.loc['A', (['bar', 'foo'], ['one', 'two'])], '\n')
# list of tuples
print(df.loc['A', [('bar', 'one'), ('foo', 'two')]], '\n')
first second
bar one -0.936493
two 1.624851
foo one 0.144425
two 0.510066
Name: A, dtype: float64
first second
bar one -0.936493
foo two 0.510066
Name: A, dtype: float64
行索引是多级索引
df = pd.DataFrame(np.random.randn(8, 4), index=arrays)
df
0 1 2 3
bar one 1.027278 -0.180081 -0.110635 0.724393
two -0.189482 -0.946654 0.910129 -0.504019
baz one -1.099056 1.757185 -0.422867 1.919558
two 0.066132 1.711503 0.033937 -0.691720
foo one -2.470554 0.587912 -2.073973 -0.550532
two -2.011376 0.480204 -1.050221 1.501428
qux one -0.117892 2.151013 0.962524 -0.183576
two 0.319457 -0.230228 1.655544 -0.364321
df.loc['bar', [0, 1]]
0 1
one 1.027278 -0.180081
two -0.189482 -0.946654
df.loc[(['bar'], 'one'), [0, 1]]
# df.loc[(['bar'], 'one'), (0, 1)]
0 1
bar one 1.027278 -0.180081
分类索引
CategoricalIndex
分类索引非常适合有重复的索引。 这是一个围绕Categorical
而创建的容器。这可以非常高效地存储和索引的具有大量重复元素的索引。
# H.get_param("pd.CategoricalIndex", globals())
from pandas.api.types import CategoricalDtype
df = pd.DataFrame({'A': np.arange(6),
'B': list('aabbca')})
df['B'] = df['B'].astype(CategoricalDtype(list('cab')))
print(df, '\n')
print(df.dtypes, '\n')
print(df.B.cat.categories, '\n')
A B
0 0 a
1 1 a
2 2 b
3 3 b
4 4 c
5 5 a
A int32
B category
dtype: object
Index(['c', 'a', 'b'], dtype='object')
通过.set_index()
建立一个有CategoricalIndex
分类索引的df2
:
df2 = df.set_index('B')
print(df2.index)
CategoricalIndex(['a', 'a', 'b', 'b', 'c', 'a'], categories=['c', 'a',
'b'], ordered=False, name='B', dtype='category')
使用 __getitem__/.iloc/.loc
进行索引,在含有重复值的索引上的工作原理相似。索引值必须在一个分类中,否者将会引发KeyError
错误。
df2.loc['a']
A
B
a 0
a 1
a 5
CategoricalIndex
在索引之后也会被保留:
df2.loc['a'].index
CategoricalIndex(['a', 'a', 'a'], categories=['c', 'a', 'b'],
ordered=False, name='B', dtype='category')
索引排序将会按照类别清单中的顺序进行(我们已经基于 CategoricalDtype(list('cab'))
建立了一个索引,因此排序的顺序是cab
)
df2.sort_index()
A
B
c 4
a 0
a 1
a 5
b 2
b 3
分组操作(Groupby)也会保留索引的全部信息。
df2.groupby(level=0).sum()
df2.groupby(level=0).sum().index
CategoricalIndex(['c', 'a', 'b'], categories=['c', 'a', 'b'],
ordered=False, name='B', dtype='category')
重设索引的操作将会根据输入的索引值返回一个索引。传入一个列表,将会返回一个最普通的Index
;如果使用类别对象Categorical
,则会返回一个分类索引CategoricalIndex
,按照其中传入的的类别值Categorical
dtype来进行索引。正如同你可以对任意pandas的索引进行重新索引一样,这将允许你随意索引任意的索引值,即便它们并不存在在你的类别对象中。
df2.reindex(['a', 'e'])
df2.reindex(['a', 'e']).index
df2.reindex(pd.Categorical(['a', 'e'], categories=list('abcde')))
df2.reindex(pd.Categorical(['a', 'e'], categories=list('abcde'))).index
间隔索引
pandas在 0.20.0 版本中新加入 IntervalIndex
和它自己特有的IntervalDtype
以及 Interval
标量类型,在pandas中,间隔数据是获得头等支持的。
IntervalIndex
间隔索引允许一些唯一的索引,并且也是 cut()
和qcut()
的返回类型
使用 间隔索引 来进行数据索引
df = pd.DataFrame({'A': [1, 2, 3, 4]},
index=pd.IntervalIndex.from_breaks([0, 1, 2, 3, 4]))
df
A
(0, 1] 1
(1, 2] 2
(2, 3] 3
(3, 4] 4
间隔索引也可以使用基于标签的索引.loc
:
# 2位于(1, 2]之中
df.loc[2]
A 2
Name: (1, 2], dtype: int64
# 2位于(1, 2]之中 3位于(2, 3]之中
df.loc[[2, 3]]
A
(1, 2] 2
(2, 3] 3
如果标签被包含在间隔当中,这个间隔也将会被选择
print(df.loc[2.5])
print(df.loc[[2.5, 3.5]])
A 3
Name: (2, 3], dtype: int64
A
(2, 3] 3
(3, 4] 4
使用 Interval
来选择数据,将只返回严格匹配(从pandas0.25.0开始)。
df.loc[pd.Interval(1, 2)]
A 2
Name: (1, 2], dtype: int64
试图选择一个没有被严格包含在 IntervalIndex
内的区间Interval
,将会出发KeyError
错误。
try:
df.loc[pd.Interval(0.5, 2.5)]
except KeyError as e:
print('KeyError:', e)
KeyError: Interval(0.5, 2.5, closed='right')
可以使用overlaps()
来创建一个布尔选择器,来选中所有与给定区间(Interval
)重复的所有区间。
idxr = df.index.overlaps(pd.Interval(0.5, 2.5))
print(idxr)
print(df[idxr])
[ True True True False]
A
(0, 1] 1
(1, 2] 2
(2, 3] 3
使用 cut
和 qcut
来为数据分块
cut()
和 qcut()
都将返回一个分类Categorical
对象,并且每个分块区域都会以 分类索引IntervalIndex
的方式被创建并保存在它的.categories
属性中。
c = pd.cut(range(4), bins=2)
print(c)
print(c.categories)
[(-0.003, 1.5], (-0.003, 1.5], (1.5, 3.0], (1.5, 3.0]]
Categories (2, interval[float64]): [(-0.003, 1.5] < (1.5, 3.0]]
IntervalIndex([(-0.003, 1.5], (1.5, 3.0]],
closed='right',
dtype='interval[float64]')
cut()
也可以接受一个 IntervalIndex
作为他的 bins
参数,这样可以使用一个非常有用的pandas的写法。 首先,我们调用 cut()
在一些数据上面,并且将 bins
设置为某一个固定的数 ,从而生成bins。
随后,我们可以在其他的数据上调用 cut()
,并传入.categories
的值,作为 bins
参数。这样新的数据就也将会被分配到同样的bins里面
pd.cut([0, 3, 5, 1], bins=c.categories)
[(-0.003, 1.5], (1.5, 3.0], NaN, (-0.003, 1.5]]
Categories (2, interval[float64]): [(-0.003, 1.5] < (1.5, 3.0]]
任何落在bins之外的数据都将会被设为 NaN
生成一定区间内的间隔
如果我们需要经常地使用步进区间,我们可以使用 interval_range()
函数,结合 start
, end
, 和 periods
来建立一个 IntervalIndex
对于数值型的间隔,默认的 interval_range
间隔频率是1,对于datetime类型的间隔则是日历日。
pd.interval_range(start=0, end=5)
pd.interval_range(start=pd.Timestamp('2017-01-01'), periods=4)
pd.interval_range(end=pd.Timedelta('3 days'), periods=3)
IntervalIndex([(0 days 00:00:00, 1 days 00:00:00], (1 days 00:00:00, 2
days 00:00:00], (2 days 00:00:00, 3 days 00:00:00]],
closed='right',
dtype='interval[timedelta64[ns]]')
freq
参数可以被用来明确非默认的频率,并且可以充分地利用各种各样的 frequency aliasesdatetime类型的时间间隔。
pd.interval_range(start=0, periods=5, freq=1.5)
pd.interval_range(start=pd.Timestamp('2017-01-01'), periods=4, freq='W')
pd.interval_range(start=pd.Timedelta('0 days'), periods=3, freq='9H')
IntervalIndex([(0 days 00:00:00, 0 days 09:00:00], (0 days 09:00:00, 0
days 18:00:00], (0 days 18:00:00, 1 days 03:00:00]],
closed='right',
dtype='interval[timedelta64[ns]]')
此外, closed
参数可以用来声明哪个边界是包含的。默认情况下,间隔的右界是包含的。
pd.interval_range(start=0, end=4, closed='both')
pd.interval_range(start=0, end=4, closed='neither')
IntervalIndex([(0, 1), (1, 2), (2, 3), (3, 4)],
closed='neither',
dtype='interval[int64]')
0.23.0版本加入的功能
使用start
, end
, 和 periods
可以从 start
到 end
(包含)生成一个平均分配的间隔,在返回IntervalIndex
中生成periods
这么多的元素(译者:区间)。
pd.interval_range(start=0, end=6, periods=4)
pd.interval_range(pd.Timestamp('2018-01-01'),
pd.Timestamp('2018-02-28'), periods=3)
IntervalIndex([(2018-01-01, 2018-01-20 08:00:00], (2018-01-20
08:00:00, 2018-02-08 16:00:00], (2018-02-08 16:00:00, 2018-02-28]],
closed='right',
dtype='interval[datetime64[ns]]')