为什么 Pandas 允许非唯一索引?

woo*_*ngs 6 python pandas

Pandas 允许唯一和非唯一索引。某些操作仅允许唯一索引。在什么情况下使用非唯一索引才有意义?我认为强制执行索引的唯一性可以帮助提前发现数据完整性问题。

Hen*_*ker 4

免责声明:独特的RangeIndex永远是性能最好的选择。这个问题似乎倾向于使用唯一索引,并且专门寻找需要允许非唯一索引的情况。因此,从现在开始,不再讨论唯一索引,也不再讨论性能,仅讨论非唯一索引的有用优势。

当我们需要跟踪数据最初来自哪里时,一般情况下非唯一索引是更可取的。很多情况下,在中间阶段,我们需要知道数据位于哪一行。这让我们可以对信息进行计算,如果索引是唯一的,这些信息要么会丢失,要么需要添加额外的列来跟踪它。下面仅举几个例子:


交错多个 DataFrame:

考虑以下 2 个 DataFrame,我们假设每个 DataFrame 代表一天的数据。我们希望按样本编号而不是按天查看每日数据:

df1 = pd.DataFrame([['10:05', 'Day 1', 'Sample 1'],
                    ['11:14', 'Day 1', 'Sample 2']])
df2 = pd.DataFrame([['10:03', 'Day 2', 'Sample 1'],
                    ['11:12', 'Day 1', 'Sample 2']])
Run Code Online (Sandbox Code Playgroud)
# df1
       0      1         2
0  10:05  Day 1  Sample 1
1  11:14  Day 1  Sample 2

#df2
       0      1         2
0  10:03  Day 2  Sample 1
1  11:12  Day 1  Sample 2
Run Code Online (Sandbox Code Playgroud)

因为 pandas 允许非唯一索引,concat所以我们可以sort_index

pd.concat([df1, df2]).sort_index()
Run Code Online (Sandbox Code Playgroud)
       0      1         2
0  10:05  Day 1  Sample 1
0  10:03  Day 2  Sample 1
1  11:14  Day 1  Sample 2
1  11:12  Day 1  Sample 2
Run Code Online (Sandbox Code Playgroud)

请注意,这是按行索引交错两个 DataFrame 的最快方法。另请注意,按列排序是不可行的12因为单词Day 1 Sample 1 将按字典顺序排序,这将遇到 、 等值的问题Day 10,或者需要大量额外的计算才能正确处理数值。

我们可以添加ignore_index=Trueto sort_index,但这只会隐藏新范围索引的覆盖,并且仍然依赖于concat返回具有非唯一索引的 DataFrame 的事实。

pd.concat([df1, df2]).sort_index(ignore_index=True)
Run Code Online (Sandbox Code Playgroud)
       0      1         2
0  10:05  Day 1  Sample 1
1  10:03  Day 2  Sample 1
2  11:14  Day 1  Sample 2
3  11:12  Day 1  Sample 2
Run Code Online (Sandbox Code Playgroud)

爆炸和减少

explode,特别是在 Series 上,是一种常见的操作,并且不丢失索引(允许重复)使得扩展和归约类型操作变得更加容易。

目标是删除列中逗号分隔字符串中的任何重复值:

df = pd.DataFrame({
    'corresponding_id': [10, 20, 30],
    'col': ['a,b,c,a', 'b,c,c,b', 'a,a,a,a']
})
Run Code Online (Sandbox Code Playgroud)

df:

   corresponding_id      col
0                10  a,b,c,a
1                20  b,c,c,b
2                30  a,a,a,a
Run Code Online (Sandbox Code Playgroud)

常见的解决方案可能类似于:

df['col'] = (
    df['col'].str.split(',').explode()
        .groupby(level=0).apply(lambda s: ','.join(np.unique(s)))
)
Run Code Online (Sandbox Code Playgroud)

df:

   corresponding_id    col
0                10  a,b,c
1                20    b,c
2                30      a
Run Code Online (Sandbox Code Playgroud)

爆炸后的结果如下:

df['col'].str.split(',').explode()

0    a
0    b
0    c
0    a
1    b
1    c
1    c
1    b
2    a
2    a
2    a
2    a
Name: col, dtype: object
Run Code Online (Sandbox Code Playgroud)

因为存在重复的索引,所以我们可以groupby相对于level=0(索引),这只有在保留索引的情况下才可能实现。如果索引不允许重复,我们将有:

0     a
1     b
2     c
3     a
4     b
5     c
6     c
7     b
8     a
9     a
10    a
11    a
Name: col, dtype: object
Run Code Online (Sandbox Code Playgroud)

无法轻松确定值来自哪些行,这使得将它们放回原位变得更加困难。


扩展数据框

使用重复标签从 DataFrame 中进行选择的能力对于扩展 DataFrame 非常有帮助。

df = pd.DataFrame({
    'Count': [2, 4],
    'Value': [1, 6]
})
Run Code Online (Sandbox Code Playgroud)

有时我们需要扩展 DataFrame,在这些情况下我们使用loc从 DataFrame 中进行选择:

df.loc[[0, 0, 1, 1, 1, 1], :]
Run Code Online (Sandbox Code Playgroud)

注意结果是:

   Count  Value
0      2      1
0      2      1
1      4      6
1      4      6
1      4      6
1      4      6
Run Code Online (Sandbox Code Playgroud)

我们能够根据重复标签从 DataFrame 中多次选择同一行(并且生成的索引不唯一)。这种情况非常常见,以至于有一种方法Index.repeat可以根据列动态执行此操作:

df.loc[df.index.repeat(df['Count']), :]

   Count  Value
0      2      1
0      2      1
1      4      6
1      4      6
1      4      6
1      4      6
Run Code Online (Sandbox Code Playgroud)