一种有效的方法是将第一个值与其余值进行比较,并使用all:
def is_unique(s):
a = s.to_numpy() # s.values (pandas<0.24)
return (a[0] == a).all()
is_unique(df['counts'])
# False
虽然最直观的想法可能是计算unique 值的数量并检查是否只有一个,但这对于我们正在尝试做的事情来说会产生不必要的高复杂性。 Numpy 的 np.unique,由 pandas 的 nunique 调用,实现了底层数组的排序,其使用 quicksort(默认)具有 O(n·log(n)) 的估计复杂度。上面的做法是O(n)。
当我们将其应用于整个数据帧时,性能差异变得更加明显(见下文)。
对于整个数据框
如果想对整个数据帧执行相同的任务,我们可以通过在all 中设置axis=0 来扩展上述内容:
def unique_cols(df):
a = df.to_numpy() # df.values (pandas<0.24)
return (a[0] == a).all(0)
对于共享示例,我们会得到:
unique_cols(df)
# array([False, False])
这是上述方法与其他一些方法(例如使用nunique(对于pd.Series)的比较)的基准:
s_num = pd.Series(np.random.randint(0, 1_000, 1_100_000))
perfplot.show(
setup=lambda n: s_num.iloc[:int(n)],
kernels=[
lambda s: s.nunique() == 1,
lambda s: is_unique(s)
],
labels=['nunique', 'first_vs_rest'],
n_range=[2**k for k in range(0, 20)],
xlabel='N'
)
以下是 pd.DataFrame 的时间安排。让我们也与numba 方法进行比较,这在这里特别有用,因为我们可以在给定列中看到重复值时立即利用捷径(注意:numba 方法仅适用于数字数据):
from numba import njit
@njit
def unique_cols_nb(a):
n_cols = a.shape[1]
out = np.zeros(n_cols, dtype=np.int32)
for i in range(n_cols):
init = a[0, i]
for j in a[1:, i]:
if j != init:
break
else:
out[i] = 1
return out
如果我们比较这三种方法:
df = pd.DataFrame(np.concatenate([np.random.randint(0, 1_000, (500_000, 200)),
np.zeros((500_000, 10))], axis=1))
perfplot.show(
setup=lambda n: df.iloc[:int(n),:],
kernels=[
lambda df: (df.nunique(0) == 1).values,
lambda df: unique_cols_nb(df.values).astype(bool),
lambda df: unique_cols(df)
],
labels=['nunique', 'unique_cols_nb', 'unique_cols'],
n_range=[2**k for k in range(0, 20)],
xlabel='N'
)