获取多索引中级别的最后一个元素
Getting the last element of a level in a multiindex
我有一个这种格式的数据框:
a b x
1 1 31
1 2 1
1 3 42
1 4 423
1 5 42
1 6 3
1 7 44
1 8 65437
1 9 73
2 1 5656
2 2 7
2 3 5
2 4 5
2 5 34
a
和b
是索引,x
是值。
我想要获取行 1 9 73
和 2 5 34
,换句话说,该级别的最后一行。
我已经弄乱了 .loc
、.iloc
和 .xs
一个小时,但我无法让它工作。我该怎么做?
使用 df
作为数据框并且列 a
已经排序,这是一种方法 -
df[np.append(np.diff(df['a'])>0,True)]
基本思想是我们沿着排序列 a
执行微分,并使用 (>0)
寻找正变化,给我们一个布尔数组。布尔数组中的 true
元素表示该列中 "group" 的结尾。由于最后一组的最后一个元素没有变化,我们需要在末尾向该布尔数组附加一个 True
元素。最后,用这样一个布尔数组索引 df
到 select 行并给我们想要的输出。
另一种方法可以建议 np.unique
使用其可选参数 return_index
,这将为我们提供每个组的第一个出现的元素的索引。因此,要使其适用于最后一个元素,只需翻转列 a
,使用 np.unique
并获取第一个出现的索引,然后从 df
中的总行数中减去它们。最后,将 df
与最终输出的索引一起索引。因此,实现将是 -
df.iloc[df.shape[0] - np.unique(df['a'][::-1],return_index=True)[1] - 1]
样本运行-
>>> df
a b x
0 1 26 46
1 1 17 32
2 1 12 65
3 1 31 96
4 1 34 10
5 1 7 80
6 1 64 50
7 1 0 34
8 1 93 28
9 2 18 92
10 2 59 22
11 2 87 31
>>> df[np.append(np.diff(df['a'])>0,True)]
a b x
8 1 93 28
11 2 87 31
>>> df.iloc[df.shape[0] - np.unique(df['a'][::-1],return_index=True)[1] - 1]
a b x
8 1 93 28
11 2 87 31
print (df.groupby('a', as_index=False).last())
a b x
0 1 9 73
1 2 5 34
如果a
和b
是MultiIndex
, first call reset_index
的水平:
print (df.reset_index().groupby('a', as_index=False).last())
a b x
0 1 9 73
1 2 5 34
特例
jezrael 提出的 groupby
解决方案是 high-level 通用解决方案。但是当 groupby
生成很多不同的组时(在 OP 提供的示例中,这将由 a
的很多不同值引起),它的性能很差。在这里,我提出了一个针对特殊情况(与 OP 的情况相匹配)的优化解决方案。
假设您有一个由 MultiIndex
索引的具有多个级别的数据框,并且这些级别的 last 的值始终在每个组中开始于相同的值;例如,假设值总是从 1
开始并向上计数。在以下示例中,这将是 number
级别。
value
name number
a 1 0.548126
b 1 0.774775
2 0.483701
3 0.820758
c 1 0.696832
2 0.905071
d 1 0.750546
2 0.761081
e 1 0.944682
2 0.336210
然后,要获取 maximum/last number
值的行的横截面,每个唯一值 name
(或任何其他级别的值的组合),你可以这样做:
df[np.roll(df.index.get_level_values('number') == 1, -1)]
你得到:
value
name number
a 1 0.548126
b 3 0.820758
c 2 0.905071
d 2 0.761081
e 2 0.336210
说明
Piece-by-piece:
df.index.get_level_values('number')
:获取每行 number
级别值的数组
df.index.get_level_values('number') == 1
:布尔数组,对于其中 number
为 1 的那些行,它们是 True
np.roll(df.index.get_level_values('number') == 1, -1)
:将前一个数组的所有值以循环方式向后移动一个位置(即第一个元素成为最后一个,第二个,第一个,依此类推)。
这个想法是,一个组的 last 值总是紧接在该组的 first 值之前,它总是1
。因此,如果我们得到一个 number
值为 1 的行的布尔掩码,我们可以将所有这些布尔值 向后 移动一个,我们得到一个掩码number
.
的最后一个值
通过循环移动 来考虑最后一行的特殊情况,以便第一个布尔值最后结束——第一行总是 number
等于1
,因此布尔值将始终为 True
,因此最后一行总是被选中(如预期的那样)。
泛型函数
def innermost_level_max(df, start_value=1, drop_level=False):
assert df.index.is_lexsorted()
level_values = df.index.get_level_values(-1)
result = df[np.roll(level_values == start_value, -1)]
if drop_level:
result = result.droplevel(-1)
return result
要玩的设置代码
import itertools as itt
import numpy as np
import pandas as pd
import perfplot
rng = np.random.default_rng(42)
def generate_names():
alphabet = [chr(i) for i in range(ord('a'), ord('z') + 1)]
for length in itt.count(1):
for tup in itt.product(*([alphabet]*length)):
yield ''.join(tup)
def make_ragged_df(n):
lengths = rng.integers(1, 3, endpoint=True, size=n)
names = np.fromiter(
itt.chain.from_iterable(itt.repeat(n, times=r) for n, r in zip(generate_names(), lengths)),
dtype='U100',
count=n
)
numbers = np.fromiter(itt.chain.from_iterable(map(range, lengths)), int, count=n) + 1
index = pd.MultiIndex.from_arrays([names, numbers], names=['name', 'number'])
data = np.random.rand(n)
df = pd.DataFrame({'value': data}, index=index)
return df
这允许您创建示例数据框:
>>> make_ragged_df(10)
value
name number
a 1 0.548126
b 1 0.774775
2 0.483701
3 0.820758
c 1 0.696832
2 0.905071
d 1 0.750546
2 0.761081
e 1 0.944682
2 0.336210
性能
使用perfplot
:
import perfplot
benchmarks = perfplot.bench(
setup=lambda n: make_ragged_df(n),
kernels=[
lambda df: df.groupby('name', sort=False).tail(1),
lambda df: df[np.roll(df.index.get_level_values('number') == 1, -1)],
],
labels=["with groupby", "with np.roll on == 1"],
n_range=range(50, 10000, 500),
xlabel="total number of rows",
)
benchmarks.show()
更特殊的情况
如果您知道 number
always 的最后一个值是什么,例如3,你只需要一个索引切片:
df.loc[pd.IndexSlice[:, 3], :]
或 cross-section:
df.xs(3, level='number')
但如果是这种情况,您可能不会一开始就阅读这个问题。
我有一个这种格式的数据框:
a b x
1 1 31
1 2 1
1 3 42
1 4 423
1 5 42
1 6 3
1 7 44
1 8 65437
1 9 73
2 1 5656
2 2 7
2 3 5
2 4 5
2 5 34
a
和b
是索引,x
是值。
我想要获取行 1 9 73
和 2 5 34
,换句话说,该级别的最后一行。
我已经弄乱了 .loc
、.iloc
和 .xs
一个小时,但我无法让它工作。我该怎么做?
使用 df
作为数据框并且列 a
已经排序,这是一种方法 -
df[np.append(np.diff(df['a'])>0,True)]
基本思想是我们沿着排序列 a
执行微分,并使用 (>0)
寻找正变化,给我们一个布尔数组。布尔数组中的 true
元素表示该列中 "group" 的结尾。由于最后一组的最后一个元素没有变化,我们需要在末尾向该布尔数组附加一个 True
元素。最后,用这样一个布尔数组索引 df
到 select 行并给我们想要的输出。
另一种方法可以建议 np.unique
使用其可选参数 return_index
,这将为我们提供每个组的第一个出现的元素的索引。因此,要使其适用于最后一个元素,只需翻转列 a
,使用 np.unique
并获取第一个出现的索引,然后从 df
中的总行数中减去它们。最后,将 df
与最终输出的索引一起索引。因此,实现将是 -
df.iloc[df.shape[0] - np.unique(df['a'][::-1],return_index=True)[1] - 1]
样本运行-
>>> df
a b x
0 1 26 46
1 1 17 32
2 1 12 65
3 1 31 96
4 1 34 10
5 1 7 80
6 1 64 50
7 1 0 34
8 1 93 28
9 2 18 92
10 2 59 22
11 2 87 31
>>> df[np.append(np.diff(df['a'])>0,True)]
a b x
8 1 93 28
11 2 87 31
>>> df.iloc[df.shape[0] - np.unique(df['a'][::-1],return_index=True)[1] - 1]
a b x
8 1 93 28
11 2 87 31
print (df.groupby('a', as_index=False).last())
a b x
0 1 9 73
1 2 5 34
如果a
和b
是MultiIndex
, first call reset_index
的水平:
print (df.reset_index().groupby('a', as_index=False).last())
a b x
0 1 9 73
1 2 5 34
特例
jezrael 提出的 groupby
解决方案是 high-level 通用解决方案。但是当 groupby
生成很多不同的组时(在 OP 提供的示例中,这将由 a
的很多不同值引起),它的性能很差。在这里,我提出了一个针对特殊情况(与 OP 的情况相匹配)的优化解决方案。
假设您有一个由 MultiIndex
索引的具有多个级别的数据框,并且这些级别的 last 的值始终在每个组中开始于相同的值;例如,假设值总是从 1
开始并向上计数。在以下示例中,这将是 number
级别。
value
name number
a 1 0.548126
b 1 0.774775
2 0.483701
3 0.820758
c 1 0.696832
2 0.905071
d 1 0.750546
2 0.761081
e 1 0.944682
2 0.336210
然后,要获取 maximum/last number
值的行的横截面,每个唯一值 name
(或任何其他级别的值的组合),你可以这样做:
df[np.roll(df.index.get_level_values('number') == 1, -1)]
你得到:
value
name number
a 1 0.548126
b 3 0.820758
c 2 0.905071
d 2 0.761081
e 2 0.336210
说明
Piece-by-piece:
df.index.get_level_values('number')
:获取每行number
级别值的数组df.index.get_level_values('number') == 1
:布尔数组,对于其中number
为 1 的那些行,它们是 np.roll(df.index.get_level_values('number') == 1, -1)
:将前一个数组的所有值以循环方式向后移动一个位置(即第一个元素成为最后一个,第二个,第一个,依此类推)。
True
这个想法是,一个组的 last 值总是紧接在该组的 first 值之前,它总是1
。因此,如果我们得到一个 number
值为 1 的行的布尔掩码,我们可以将所有这些布尔值 向后 移动一个,我们得到一个掩码number
.
通过循环移动 来考虑最后一行的特殊情况,以便第一个布尔值最后结束——第一行总是 number
等于1
,因此布尔值将始终为 True
,因此最后一行总是被选中(如预期的那样)。
泛型函数
def innermost_level_max(df, start_value=1, drop_level=False):
assert df.index.is_lexsorted()
level_values = df.index.get_level_values(-1)
result = df[np.roll(level_values == start_value, -1)]
if drop_level:
result = result.droplevel(-1)
return result
要玩的设置代码
import itertools as itt
import numpy as np
import pandas as pd
import perfplot
rng = np.random.default_rng(42)
def generate_names():
alphabet = [chr(i) for i in range(ord('a'), ord('z') + 1)]
for length in itt.count(1):
for tup in itt.product(*([alphabet]*length)):
yield ''.join(tup)
def make_ragged_df(n):
lengths = rng.integers(1, 3, endpoint=True, size=n)
names = np.fromiter(
itt.chain.from_iterable(itt.repeat(n, times=r) for n, r in zip(generate_names(), lengths)),
dtype='U100',
count=n
)
numbers = np.fromiter(itt.chain.from_iterable(map(range, lengths)), int, count=n) + 1
index = pd.MultiIndex.from_arrays([names, numbers], names=['name', 'number'])
data = np.random.rand(n)
df = pd.DataFrame({'value': data}, index=index)
return df
这允许您创建示例数据框:
>>> make_ragged_df(10)
value
name number
a 1 0.548126
b 1 0.774775
2 0.483701
3 0.820758
c 1 0.696832
2 0.905071
d 1 0.750546
2 0.761081
e 1 0.944682
2 0.336210
性能
使用perfplot
:
import perfplot
benchmarks = perfplot.bench(
setup=lambda n: make_ragged_df(n),
kernels=[
lambda df: df.groupby('name', sort=False).tail(1),
lambda df: df[np.roll(df.index.get_level_values('number') == 1, -1)],
],
labels=["with groupby", "with np.roll on == 1"],
n_range=range(50, 10000, 500),
xlabel="total number of rows",
)
benchmarks.show()
更特殊的情况
如果您知道 number
always 的最后一个值是什么,例如3,你只需要一个索引切片:
df.loc[pd.IndexSlice[:, 3], :]
或 cross-section:
df.xs(3, level='number')
但如果是这种情况,您可能不会一开始就阅读这个问题。