Python 数据处理 —— pandas 索引类型

MultiIndex 排序

可以使用 sort_index()MultiIndex 排序

In [101]: import random

In [102]: random.shuffle(tuples)

In [103]: s = pd.Series(np.random.randn(8), index=pd.MultiIndex.from_tuples(tuples))

In [104]: s
Out[104]: 
foo  one    0.206053
qux  two   -0.251905
bar  two   -2.213588
baz  one    1.063327
bar  one    1.266143
foo  two    0.299368
qux  one   -0.863838
baz  two    0.408204
dtype: float64

In [105]: s.sort_index()
Out[105]: 
bar  one    1.266143
     two   -2.213588
baz  one    1.063327
     two    0.408204
foo  one    0.206053
     two    0.299368
qux  one   -0.863838
     two   -0.251905
dtype: float64

In [106]: s.sort_index(level=0)
Out[106]: 
bar  one    1.266143
     two   -2.213588
baz  one    1.063327
     two    0.408204
foo  one    0.206053
     two    0.299368
qux  one   -0.863838
     two   -0.251905
dtype: float64

In [107]: s.sort_index(level=1)
Out[107]: 
bar  one    1.266143
baz  one    1.063327
foo  one    0.206053
qux  one   -0.863838
bar  two   -2.213588
baz  two    0.408204
foo  two    0.299368
qux  two   -0.251905
dtype: float64

如果命名了 MultiIndex 的级别,你也可以给 sort_index 传递一个级别名称

In [108]: s.index.set_names(["L1", "L2"], inplace=True)

In [109]: s.sort_index(level="L1")
Out[109]: 
L1   L2 
bar  one    1.266143
     two   -2.213588
baz  one    1.063327
     two    0.408204
foo  one    0.206053
     two    0.299368
qux  one   -0.863838
     two   -0.251905
dtype: float64

In [110]: s.sort_index(level="L2")
Out[110]: 
L1   L2 
bar  one    1.266143
baz  one    1.063327
foo  one    0.206053
qux  one   -0.863838
bar  two   -2.213588
baz  two    0.408204
foo  two    0.299368
qux  two   -0.251905
dtype: float64

在较高维的对象上,如果有 MultiIndex,你可以按级别对其他轴进行排序

In [111]: df.T.sort_index(level=1, axis=1)
Out[111]: 
        one      zero       one      zero
          x         x         y         y
0  0.600178  2.410179  1.519970  0.132885
1  0.274230  1.450520 -0.493662 -0.023688

即使数据没有排序,索引也可以工作,但效率相当低(并显示 PerformanceWarning)。它还将返回数据的拷贝,而不是视图

In [112]: dfm = pd.DataFrame(
   .....:     {"jim": [0, 0, 1, 1], "joe": ["x", "x", "z", "y"], "jolie": np.random.rand(4)}
   .....: )
   .....: 

In [113]: dfm = dfm.set_index(["jim", "joe"])

In [114]: dfm
Out[114]: 
            jolie
jim joe          
0   x    0.490671
    x    0.120248
1   z    0.537020
    y    0.110968
>>> dfm.loc[(1, 'z')]
PerformanceWarning: indexing past lexsort depth may impact performance.

           jolie
jim joe
1   z    0.64094

此外,如果你试图索引一些没有完全 lexsorted 的索引,这可能会引发

>>> dfm.loc[(0, 'y'):(1, 'z')]
UnsortedIndexError: 'Key length (2) was greater than MultiIndex lexsort depth (1)'

MultiIndex 上的 is_lexsorted() 方法会显示索引是否排序,而 lexsort_depth 属性会返回排序深度

In [115]: dfm.index.is_lexsorted()
Out[115]: False

In [116]: dfm.index.lexsort_depth
Out[116]: 1
In [117]: dfm = dfm.sort_index()

In [118]: dfm
Out[118]: 
            jolie
jim joe          
0   x    0.490671
    x    0.120248
1   y    0.110968
    z    0.537020

In [119]: dfm.index.is_lexsorted()
Out[119]: True

In [120]: dfm.index.lexsort_depth
Out[120]: 2

take 方法

NumPy ndarrays 类似,pandasIndexSeriesDataFrame 也提供了 take() 方法,用于沿着给定的轴并按给定的索引获取所有元素

这个给定的索引必须是一个列表或整数索引位置的 ndarraytake 也接受负整数作为对象末端的相对位置

In [122]: index = pd.Index(np.random.randint(0, 1000, 10))

In [123]: index
Out[123]: Int64Index([214, 502, 712, 567, 786, 175, 993, 133, 758, 329], dtype='int64')

In [124]: positions = [0, 9, 3]

In [125]: index[positions]
Out[125]: Int64Index([214, 329, 567], dtype='int64')

In [126]: index.take(positions)
Out[126]: Int64Index([214, 329, 567], dtype='int64')

In [127]: ser = pd.Series(np.random.randn(10))

In [128]: ser.iloc[positions]
Out[128]: 
0   -0.179666
9    1.824375
3    0.392149
dtype: float64

In [129]: ser.take(positions)
Out[129]: 
0   -0.179666
9    1.824375
3    0.392149
dtype: float64

对于 DataFrames,给定的索引应该是一个一维的 listndarray,用于指定行或列的位置

In [130]: frm = pd.DataFrame(np.random.randn(5, 3))

In [131]: frm.take([1, 4, 3])
Out[131]: 
          0         1         2
1 -1.237881  0.106854 -1.276829
4  0.629675 -1.425966  1.857704
3  0.979542 -1.633678  0.615855

In [132]: frm.take([0, 2], axis=1)
Out[132]: 
          0         2
0  0.595974  0.601544
1 -1.237881 -1.276829
2 -0.767101  1.499591
3  0.979542  0.615855
4  0.629675  1.857704

需要注意的是,pandas 对象上的 take 方法并不支持布尔索引,如果传入布尔索引可能会返回意想不到的结果

In [133]: arr = np.random.randn(10)

In [134]: arr.take([False, False, True, True])
Out[134]: array([-1.1935, -1.1935,  0.6775,  0.6775])

In [135]: arr[[0, 1]]
Out[135]: array([-1.1935,  0.6775])

In [136]: ser = pd.Series(np.random.randn(10))

In [137]: ser.take([False, False, True, True])
Out[137]: 
0    0.233141
0    0.233141
1   -0.223540
1   -0.223540
dtype: float64

In [138]: ser.iloc[[0, 1]]
Out[138]: 
0    0.233141
1   -0.223540
dtype: float64

最后,要注意一下性能,因为 take 方法处理的输入范围更窄,所以它的性能比华丽的索引快得多

In [139]: arr = np.random.randn(10000, 5)

In [140]: indexer = np.arange(10000)

In [141]: random.shuffle(indexer)

In [142]: %timeit arr[indexer]
   .....: %timeit arr.take(indexer, axis=0)
   .....: 
127 us +- 535 ns per loop (mean +- std. dev. of 7 runs, 10000 loops each)
37.6 us +- 224 ns per loop (mean +- std. dev. of 7 runs, 10000 loops each)
In [143]: ser = pd.Series(arr[:, 0])

In [144]: %timeit ser.iloc[indexer]
   .....: %timeit ser.take(indexer)
   .....: 
71.2 us +- 624 ns per loop (mean +- std. dev. of 7 runs, 10000 loops each)
62.8 us +- 565 ns per loop (mean +- std. dev. of 7 runs, 10000 loops each)

索引类型

在前面几节中,我们已经讨论了很多 MultiIndex

在下面的小节中,我们将重点介绍其他一些索引类型

1 CategoricalIndex

CategoricalIndex 是一种用于支持重复索引的索引类型。这是一个围绕分类的容器,允许高效地索引和存储具有大量重复元素的索引

In [145]: from pandas.api.types import CategoricalDtype

In [146]: df = pd.DataFrame({"A": np.arange(6), "B": list("aabbca")})

In [147]: df["B"] = df["B"].astype(CategoricalDtype(list("cab")))

In [148]: df
Out[148]: 
   A  B
0  0  a
1  1  a
2  2  b
3  3  b
4  4  c
5  5  a

In [149]: df.dtypes
Out[149]: 
A       int64
B    category
dtype: object

In [150]: df["B"].cat.categories
Out[150]: Index(['c', 'a', 'b'], dtype='object')

将其设置为索引会创建 CategoricalIndex

In [151]: df2 = df.set_index("B")

In [152]: df2.index
Out[152]: CategoricalIndex(['a', 'a', 'b', 'b', 'c', 'a'], categories=['c', 'a', 'b'], ordered=False, name='B', dtype='category')

使用 __getitem__/.iloc/.loc 的工作方式类似于重复的 Index

索引必须包含在类别中,否则操作将引发 KeyError

In [153]: df2.loc["a"]
Out[153]: 
   A
B   
a  0
a  1
a  5

索引后,CategoricalIndex 被保留

In [154]: df2.loc["a"].index
Out[154]: CategoricalIndex(['a', 'a', 'a'], categories=['c', 'a', 'b'], ordered=False, name='B', dtype='category')

对索引排序将按类别的顺序排序,我们用 CategoricalDtype(list('cab'))) 创建了索引,因此排序的顺序是 cab

In [155]: df2.sort_index()
Out[155]: 
   A
B   
c  4
a  0
a  1
a  5
b  2
b  3

对索引的 Groupby 操作也将保留索引的性质

In [156]: df2.groupby(level=0).sum()
Out[156]: 
   A
B   
c  4
a  6
b  5

In [157]: df2.groupby(level=0).sum().index
Out[157]: CategoricalIndex(['c', 'a', 'b'], categories=['c', 'a', 'b'], ordered=False, name='B', dtype='category')

重新索引操作将根据所传递索引器的类型返回结果索引。传递一个列表将返回一个普通的索引

使用 Categorical 进行索引将返回一个 CategoricalIndex,根据传入的 Categorical dtype 的类别建立索引。

这允许我们任意索引这些甚至不在类别中的值,类似于你重新索引任何 pandas 索引。

In [158]: df3 = pd.DataFrame(
   .....:     {"A": np.arange(3), "B": pd.Series(list("abc")).astype("category")}
   .....: )
   .....: 

In [159]: df3 = df3.set_index("B")

In [160]: df3
Out[160]: 
   A
B   
a  0
b  1
c  2
In [161]: df3.reindex(["a", "e"])
Out[161]: 
     A
B     
a  0.0
e  NaN

In [162]: df3.reindex(["a", "e"]).index
Out[162]: Index(['a', 'e'], dtype='object', name='B')

In [163]: df3.reindex(pd.Categorical(["a", "e"], categories=list("abe")))
Out[163]: 
     A
B     
a  0.0
e  NaN

In [164]: df3.reindex(pd.Categorical(["a", "e"], categories=list("abe"))).index
Out[164]: CategoricalIndex(['a', 'e'], categories=['a', 'b', 'e'], ordered=False, name='B', dtype='category')

注意

CategoricalIndex 形状重构的和比较操作必须具有相同的类别,否则将引发 TypeError

In [165]: df4 = pd.DataFrame({"A": np.arange(2), "B": list("ba")})

In [166]: df4["B"] = df4["B"].astype(CategoricalDtype(list("ab")))

In [167]: df4 = df4.set_index("B")

In [168]: df4.index
Out[168]: CategoricalIndex(['b', 'a'], categories=['a', 'b'], ordered=False, name='B', dtype='category')

In [169]: df5 = pd.DataFrame({"A": np.arange(2), "B": list("bc")})

In [170]: df5["B"] = df5["B"].astype(CategoricalDtype(list("bc")))

In [171]: df5 = df5.set_index("B")

In [172]: df5.index
Out[172]: CategoricalIndex(['b', 'c'], categories=['b', 'c'], ordered=False, name='B', dtype='category')
>>> pd.concat([df4, df5])
TypeError: categories must match existing categories when appending
2 Int64Index 和 RangeIndex

Int64Indexpandas 中的一个基本的索引。这是一个不可变的数组,实现了一个有序的、可切片的集合。

RangeIndexInt64Index 的一个子类,它为所有 NDFrame 对象提供默认索引。

RangeIndexInt64Index 的优化版本,可以表示单调有序集。它们类似于 Python 的范围类型

3 Float64Index

默认情况下,当在创建索引时传递浮点值或混合整型浮点值时,将自动创建 Float64Index

这就实现了一个纯粹的基于标签的切片范式,使得 [], ix, loc 用于标量索引和切片的工作方式完全相同

In [173]: indexf = pd.Index([1.5, 2, 3, 4.5, 5])

In [174]: indexf
Out[174]: Float64Index([1.5, 2.0, 3.0, 4.5, 5.0], dtype='float64')

In [175]: sf = pd.Series(range(5), index=indexf)

In [176]: sf
Out[176]: 
1.5    0
2.0    1
3.0    2
4.5    3
5.0    4
dtype: int64

[] , .loc 的标量选择将始终基于标签。整数将匹配相等的浮点索引(例如 3 等于 3.0

In [177]: sf[3]
Out[177]: 2

In [178]: sf[3.0]
Out[178]: 2

In [179]: sf.loc[3]
Out[179]: 2

In [180]: sf.loc[3.0]
Out[180]: 2

唯一的位置索引是通过 iloc

In [181]: sf.iloc[3]
Out[181]: 3

没有找到标量索引将引发 KeyError。在使用 []ixloc 时,切片主要取决于索引的值,而在使用 iloc 时总是基于位置。

当切片是布尔型时例外,在这种情况下它将始终是有位置的。

In [182]: sf[2:4]
Out[182]: 
2.0    1
3.0    2
dtype: int64

In [183]: sf.loc[2:4]
Out[183]: 
2.0    1
3.0    2
dtype: int64

In [184]: sf.iloc[2:4]
Out[184]: 
3.0    2
4.5    3
dtype: int64

float 索引中,允许使用 float 进行切片

In [185]: sf[2.1:4.6]
Out[185]: 
3.0    2
4.5    3
dtype: int64

In [186]: sf.loc[2.1:4.6]
Out[186]: 
3.0    2
4.5    3
dtype: int64

在非浮点型索引中,使用浮点型进行切片将引发 TypeError

In [1]: pd.Series(range(5))[3.5]
TypeError: the label [3.5] is not a proper indexer for this index type (Int64Index)

In [1]: pd.Series(range(5))[3.5:4.5]
TypeError: the slice start [3.5] is not a proper indexer for this index type (Int64Index)

下面是使用这种索引的一个典型的例子。假设您有一个不规则的类似 timedelta 的索引,但是数据是以浮点数的形式记录的。例如,这可以是毫秒的偏移量

In [187]: dfir = pd.concat(
   .....:     [
   .....:         pd.DataFrame(
   .....:             np.random.randn(5, 2), index=np.arange(5) * 250.0, columns=list("AB")
   .....:         ),
   .....:         pd.DataFrame(
   .....:             np.random.randn(6, 2),
   .....:             index=np.arange(4, 10) * 250.1,
   .....:             columns=list("AB"),
   .....:         ),
   .....:     ]
   .....: )
   .....: 

In [188]: dfir
Out[188]: 
               A         B
0.0    -0.435772 -1.188928
250.0  -0.808286 -0.284634
500.0  -1.815703  1.347213
750.0  -0.243487  0.514704
1000.0  1.162969 -0.287725
1000.4 -0.179734  0.993962
1250.5 -0.212673  0.909872
1500.6 -0.733333 -0.349893
1750.7  0.456434 -0.306735
2000.8  0.553396  0.166221
2250.9 -0.101684 -0.734907

对于所有选择操作将始终以值为基础工作

In [189]: dfir[0:1000.4]
Out[189]: 
               A         B
0.0    -0.435772 -1.188928
250.0  -0.808286 -0.284634
500.0  -1.815703  1.347213
750.0  -0.243487  0.514704
1000.0  1.162969 -0.287725
1000.4 -0.179734  0.993962

In [190]: dfir.loc[0:1001, "A"]
Out[190]: 
0.0      -0.435772
250.0    -0.808286
500.0    -1.815703
750.0    -0.243487
1000.0    1.162969
1000.4   -0.179734
Name: A, dtype: float64

In [191]: dfir.loc[1000.4]
Out[191]: 
A   -0.179734
B    0.993962
Name: 1000.4, dtype: float64

你可以检索前 1 秒(1000 毫秒)的数据

In [192]: dfir[0:1000]
Out[192]: 
               A         B
0.0    -0.435772 -1.188928
250.0  -0.808286 -0.284634
500.0  -1.815703  1.347213
750.0  -0.243487  0.514704
1000.0  1.162969 -0.287725

如果你需要基于整数位置进行选择,你应该使用 iloc

In [193]: dfir.iloc[0:5]
Out[193]: 
               A         B
0.0    -0.435772 -1.188928
250.0  -0.808286 -0.284634
500.0  -1.815703  1.347213
750.0  -0.243487  0.514704
1000.0  1.162969 -0.287725
4 IntervalIndex

IntervalIndex 和它对应的类型 IntervalDtype,即 Interval 标量类型,允许在 pandas 中对区间符号提供支持

IntervalIndex 允许一些唯一的索引,并且也用作 cut()qcut() 中的返回类型

4.1 使用 IntervalIndex 进行索引

IntervalIndex 可以在 SeriesDataFrame 中作为索引使用

In [194]: df = pd.DataFrame(
   .....:     {"A": [1, 2, 3, 4]}, index=pd.IntervalIndex.from_breaks([0, 1, 2, 3, 4])
   .....: )
   .....: 

In [195]: df
Out[195]: 
        A
(0, 1]  1
(1, 2]  2
(2, 3]  3
(3, 4]  4

通过 .loc 沿着区间的边缘进行基于标签的索引,就像你期望的那样,选择那个特定的区间

In [196]: df.loc[2]
Out[196]: 
A    2
Name: (1, 2], dtype: int64

In [197]: df.loc[[2, 3]]
Out[197]: 
        A
(1, 2]  2
(2, 3]  3

如果您选择一个包含在一个区间内的标签,这也将选择这个区间

In [198]: df.loc[2.5]
Out[198]: 
A    3
Name: (2, 3], dtype: int64

In [199]: df.loc[[2.5, 3.5]]
Out[199]: 
        A
(2, 3]  3
(3, 4]  4

使用 Interval 索引选择将只返回精确匹配

In [200]: df.loc[pd.Interval(1, 2)]
Out[200]: 
A    2
Name: (1, 2], dtype: int64

试图选择一个不完全包含在 IntervalIndex 中的 Interval 将引发一个 KeyError

In [7]: df.loc[pd.Interval(0.5, 2.5)]
---------------------------------------------------------------------------
KeyError: Interval(0.5, 2.5, closed='right')

可以使用 overlaps() 方法来选择与给定 Interval 重叠的所有 Intervals,从而创建一个布尔索引器

In [201]: idxr = df.index.overlaps(pd.Interval(0.5, 2.5))

In [202]: idxr
Out[202]: array([ True,  True,  True, False])

In [203]: df[idxr]
Out[203]: 
        A
(0, 1]  1
(1, 2]  2
(2, 3]  3
4.2 用 cut 和 qcut 来装箱数据

cut()qcut() 都返回一个 Categorical 对象,它们创建的 binsIntervalIndex 的形式存储在其 .categories 属性中

In [204]: c = pd.cut(range(4), bins=2)

In [205]: c
Out[205]: 
[(-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]]

In [206]: c.categories
Out[206]: 
IntervalIndex([(-0.003, 1.5], (1.5, 3.0]],
              closed='right',
              dtype='interval[float64]')

cut() 也接受一个 IntervalIndex 作为它的 bins 参数,首先,我们在调用 cut() 时,将一些数据和 bins 参数设置为一个固定的数字,以生成 bins

然后,我们将 .category 的值传递给后续调用 cut() 函数的 bins 参数,新的数据将被分到对应的 bins 中。

In [207]: pd.cut([0, 3, 5, 1], bins=c.categories)
Out[207]: 
[(-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

4.3 生成区间范围

如果需要固定间隔的区间,可以使用 interval_range() 函数来创建 IntervalIndex,使用不同的 startendperiods

interval_range 的默认频率为数字间隔 1

In [208]: pd.interval_range(start=0, end=5)
Out[208]: 
IntervalIndex([(0, 1], (1, 2], (2, 3], (3, 4], (4, 5]],
              closed='right',
              dtype='interval[int64]')

In [209]: pd.interval_range(start=pd.Timestamp("2017-01-01"), periods=4)
Out[209]: 
IntervalIndex([(2017-01-01, 2017-01-02], (2017-01-02, 2017-01-03], (2017-01-03, 2017-01-04], (2017-01-04, 2017-01-05]],
              closed='right',
              dtype='interval[datetime64[ns]]')

In [210]: pd.interval_range(end=pd.Timedelta("3 days"), periods=3)
Out[210]: 
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 参数可以用来指定频率,并且可以使用各种类似于 datetime 间隔的频率别名

In [211]: pd.interval_range(start=0, periods=5, freq=1.5)
Out[211]: 
IntervalIndex([(0.0, 1.5], (1.5, 3.0], (3.0, 4.5], (4.5, 6.0], (6.0, 7.5]],
              closed='right',
              dtype='interval[float64]')

In [212]: pd.interval_range(start=pd.Timestamp("2017-01-01"), periods=4, freq="W")
Out[212]: 
IntervalIndex([(2017-01-01, 2017-01-08], (2017-01-08, 2017-01-15], (2017-01-15, 2017-01-22], (2017-01-22, 2017-01-29]],
              closed='right',
              dtype='interval[datetime64[ns]]')

In [213]: pd.interval_range(start=pd.Timedelta("0 days"), periods=3, freq="9H")
Out[213]: 
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 参数可用于指定间隔在哪边关闭。默认情况下,区间是左开右闭

In [214]: pd.interval_range(start=0, end=4, closed="both")
Out[214]: 
IntervalIndex([[0, 1], [1, 2], [2, 3], [3, 4]],
              closed='both',
              dtype='interval[int64]')

In [215]: pd.interval_range(start=0, end=4, closed="neither")
Out[215]: 
IntervalIndex([(0, 1), (1, 2), (2, 3), (3, 4)],
              closed='neither',
              dtype='interval[int64]')

指定 startendperiods 将生成一个从开始到结束均匀间隔的区间,返回的 IntervalIndex 中包含 periods 个元素

In [216]: pd.interval_range(start=0, end=6, periods=4)
Out[216]: 
IntervalIndex([(0.0, 1.5], (1.5, 3.0], (3.0, 4.5], (4.5, 6.0]],
              closed='right',
              dtype='interval[float64]')

In [217]: pd.interval_range(pd.Timestamp("2018-01-01"), pd.Timestamp("2018-02-28"), periods=3)
Out[217]: 
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]]')
  • 0
    点赞
  • 0
    收藏
    觉得还不错? 一键收藏
  • 打赏
    打赏
  • 0
    评论
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包

打赏作者

名本无名

你的鼓励将是我创作的最大动力

¥1 ¥2 ¥4 ¥6 ¥10 ¥20
扫码支付:¥1
获取中
扫码支付

您的余额不足,请更换扫码支付或充值

打赏作者

实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

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

余额充值