当元组中每个位置的值来自不同的列表时,创建一个元组序列中值的索引数组



这很难解释,但我有一个元组列表,其中每个元组的长度为n,每个元组中项的可能值包含在一组列表中(一个列表对应元组中的每个位置)。

为了一个简单的例子,我的元组列表可能是
sequence = [('b', 1), ('c', 2), ('c', 1), ('a', 3), ('c', 2), ('a', 3)]

可能的值列表如下:

state_lists = [['a', 'b', 'c'], [1, 2, 3, 4]]

我想要一种有效的方法来创建类似于sequence的列表或数组(首选整数数组),但每个值都由state_lists中相应列表中的索引替换。

这是一个利用np.searchsorted的解决方案。不幸的是,这个函数只支持一维数组:

import numpy as np
sequence_as_array = np.array(sequence)
sequence_indexes = [
np.searchsorted(states, sequence_as_array[:, i])
for i, states in enumerate(state_lists)
]
lookup_array = np.vstack(sequence_indexes).T
print(lookup_array)
[[1 0]
[2 1]
[2 0]
[0 2]
[2 1]
[0 2]]

是否有更简单的方法来做到这一点(例如,没有for循环,将sequence转换为数组并避免vstack)?

必须是通解,因为元组的长度可以大于2,数据类型可以是float、int或string。

替换值的原因是为了创建一个查找数组,以便快速索引和比存储原始值更少的内存。sequence的长度可以超过10,000。

一种有效的方法是使用pandas中内置的对分类序列的支持:

import pandas as pd
sequence = [('b', 1), ('c', 2), ('c', 1), ('a', 3), ('c', 2), ('a', 3)]
df = pd.DataFrame(sequence, dtype='category')
print(df[0].cat.categories.to_numpy())
# ['a', 'b', 'c']
print(df[0].cat.codes.to_numpy())
# [1 2 2 0 2 0]

Pandas已经为这类事情相当优化了代码路径,您可以根据您的用例利用它们,而不必自己重新实现它们。

使用字典的反向状态索引映射?

state_dict = [{s: i for i,s in enumerate(state)} for state in state_lists]
lookup_array = np.array([[s[y] for s,y in zip(state_dict,x)] for x in sequence])

这是我刚刚发现的一种避免创建数组的解决方案:

lookup_array = np.empty((len(sequence), len(state_lists)), dtype=int)
for i, item_seq in enumerate(zip(*sequence)):
lookup_array[:, i] = np.searchsorted(state_lists[i], item_seq)
print(lookup_array)
[[1 0]
[2 1]
[2 0]
[0 2]
[2 1]
[0 2]]

从@jakevdp的回答中得到提示,我们还可以使用Pandas MultiIndex.from_tuples方法来实现目标:

import pandas as pd
seq_as_multiindex = pd.MultiIndex.from_tuples(sequence)
lookup_array = np.array(seq_as_multiindex.codes).T
print(lookup_array)
[[1 0]
[2 1]
[2 0]
[0 2]
[2 1]
[0 2]]

然而,有一个警告。此方法不使用state_lists来设置分类索引,因此返回的值可能不匹配。为了避免这种情况,您可以获得实际的类别标签,如下所示:

print(seq_as_multiindex.levels)
[['a', 'b', 'c'], [1, 2, 3]]

(注意,这与state_lists略有不同。第二个列表中没有4)。

最新更新