查找最小数字的索引

Posted

技术标签:

【中文标题】查找最小数字的索引【英文标题】:Finding the smallest number's index 【发布时间】:2021-11-06 19:23:07 【问题描述】:

我有以下(采样的)字典 A,它最初有超过 17,000 个键,每个数组的长度刚刚超过 600,000(对所有人来说都是一样的)。我试图为 600,000 个输入中的每一个找到数组中最小数字的键。例如,在下面的字典中,我想得到 i = 3093094 for j = 0 因为 45.16672136 是所有数组的第一个索引中最小的。同样,对于 j = 1,i = 1157086 因为 1.53174068 是最小的。

A = 3093094: array([45.16672136,  1.68053313, 13.78822307, ..., 36.18798239,
        36.09565274, 35.85261821]),
 1156659: array([45.46286695,  1.69632425, 13.81351489, ..., 36.54544469,
        36.45329774, 36.20969689]),
 1156667: array([45.43970605,  1.69026244, 13.81365067, ..., 36.51934187,
        36.42716964, 36.18364528]),
 1156792: array([45.29956347,  1.57736575, 13.90834355, ..., 36.43079348,
        36.33804273, 36.09623309]),
 1157086: array([45.38149498,  1.53174068, 13.98398836, ..., 36.57985343,
        36.48684657, 36.2457831 ]),
 1430072: array([45.46114909,  1.58096885, 13.95459557, ..., 36.64775128,
        36.55496457, 36.31324461]),
 1668445: array([45.44073352,  1.5941793 , 13.92953699, ..., 36.60630965,
        36.51361336, 36.27162926]),
 3055958: array([45.45006118,  1.57686417, 13.95499241, ..., 36.63558996,
        36.54278917, 36.30111176]),
 1078241: array([45.56175847,  1.77256163, 13.75586274, ..., 36.61441986,
        36.52264105, 36.27795081])

我有以下多处理解决方案方法,但由于处理时间太长,正在寻找更有效的方法。

import numpy as np
import os
from multiprocessing import Pool


C = range(len(A[3093094]))

def closest(All_inputs):
    (A,j) = All_inputs
    B = list(A.keys())
    my_list = [A[i][j] for i in B]
    return(B[np.argmin(np.array(my_list))])

with Pool(processes=os.cpu_count()) as pool:
    results = pool.map(closest, [(A,j) for j in C])

一个挑战是在多处理中复制 A,因为它的大小很大。你有什么 Pythonic 方法可以快速完成这个看似微不足道的计算吗?

【问题讨论】:

也许你可以把你的字典切成块?之后你可以在线程中使用这个块 在我的经验中,对字典进行切片是最耗时的部分。我认为,my_list = [A[i][j] for i in B] 正在做切片。如果我在多处理之前进行切片,那么我会以串行方式进行大部分计算。否则,我复制一个巨大的字典... 第二个想法:你能对你的输入进行排序吗?你有一个 [key][0] - 始终是数组的最小值 然后,我丢失了每个数组中的顺序,不知道是否将 A[key][0] 与 A[another_key][0] 进行比较。我也看不出它有什么帮助。我不是试图找到每个键的最小值的数组索引。 【参考方案1】:

如果你的内存足够大。也许你可以试试这个,使用熊猫。如果仍然很慢,请尝试使用 dask。这两个示例都在下面列出。

import numpy as np
import pandas as pd
import dask.dataframe as dd
from tqdm import tqdm
test_data = 
for i in tqdm(range(2000)):
    test_data[i] = np.random.randint(0, 10000, 600000)

# test one
# print(test_data)
now = time.time()
df = pd.DataFrame(test_data)
min_idx = df.idxmin(axis=1)
result_one = dict(zip(range(2000), min_idx.tolist()))
# print(result_one)
print(time.time() - now)

# test two
now = time.time()
df = pd.DataFrame(test_data)
ddf = dd.from_pandas(df, npartitions=multiprocessing.cpu_count())
min_idx = ddf.idxmin(axis=1).compute(scheduler="processes")
result_two = dict(zip(range(2000), min_idx.tolist()))
# print(result_two)
print(time.time() - now)

【讨论】:

感谢您的回答,但我并不想找到每个键的最小值的数组索引。 df.idxmin(axis=1); result_one = dict(zip(range(2000), min_idx.tolist())) 在大约 40 分钟内完成所需的操作。 抱歉,我休息了几天。我根据你的cmets更改了答案。【参考方案2】:

这似乎有效,并且应该比将每一列转换为具有非 Python 列表理解的 Python 列表然后返回 NumPy 数组更快:

K = np.array(list(A))
V = np.array(list(A.values()))
print(K[V.argmin(axis=0)])

示例数据的输出(删除了...):

[3093094 1157086 1078241 3093094 3093094 3093094]

Try it online!

【讨论】:

V = np.array(list(A.values())) 大约需要 5 分钟。但是,K[V.argmin(axis=0)] 需要很长时间。 @tcokyasar V = np.array([v[0:1000] for v in A.values()]) 需要多长时间?之后K[V.argmin(axis=0)] 需要多长时间?【参考方案3】:

我在一台 12 核和 16G RAM 的机器上尝试了以下操作:

from multiprocessing import Pool, cpu_count
from time import perf_counter

def closest(values):
    return np.argmin(np.array(values))

if __name__ == "__main__":
    # Build A inside __main__ (otherwise each process builds it again)
    num_keys = 10_000
    arr_len = 100_000
    rng = np.random.default_rng()
    A = 
        key: rng.integers(0, 1000, arr_len)
        for key in range(1000, 1000 + num_keys)
    

    # Multiprocessing
    start = perf_counter()
    with Pool(processes=cpu_count()) as p:
        indices = p.imap(closest, zip(*A.values()), chunksize=1000)
        keys = tuple(A.keys())
        results = [keys[i] for i in indices]
    end = perf_counter()
    print(f"Duration (np.argmin mp): end - start:.2f")

    # np.argmin directly
    start = perf_counter()
    arr = np.array([*A.values()])
    keys = tuple(A.keys())
    results = [keys[i] for i in np.argmin(arr, axis=0)]
    end = perf_counter()
    print(f"Duration (np.argmin direct): end - start:.2f")

持续时间结果:

Duration (np.argmin mp): 1258.07
Duration (np.argmin direct): 563.84

小样本的结果(num_keys = 4arr_len = 8):

A =
1000: [879, 130, 114, 973, 691, 394, 122, 215],
 1001: [221, 482, 510, 319, 454, 585, 767, 138],
 1002: [982, 526, 971, 168, 185, 477, 838, 37],
 1003: [675, 293, 769, 878, 611, 695, 237, 129]
results = [1001, 1000, 1000, 1002, 1002, 1000, 1000, 1002]
results = [1001, 1000, 1000, 1002, 1002, 1000, 1000, 1002]

【讨论】:

感谢您的回答。我将实施并查看我的案例的持续时间。但我有个问题。我使用 Jupyter Notebook,它处理多处理有点奇怪,这总是让我感到困惑。为了让它工作,我将我的函数放在一个单独的 py 文件中,这需要我将所有必要的局部变量(在 ipynb 上定义)移动到这个 py 以进行多处理。基本上,我从 py 文件调用函数并在 ipynb 中使用多处理运行。您知道上述方法(将并行使用的预期功能推到顶部并用__main__ 包装其余功能)是否适用于 ipynb? 对不起,有不相关的问题,但是为什么 chunksize = 1000? @tcokyasar 关于您的第一条评论:不幸的是,我对 Jupyter Notebooks 几乎一无所知。关于chunksize:没有明确的规定什么是最佳尺寸。我通常会弹奏一下来调整它。所以,我并不是说 1000 是最佳的(我应该说清楚)。无论如何,如果没有多处理的 2. 版本可以工作,我建议尝试一下,因为在我看过的所有场景中,它似乎更快。

以上是关于查找最小数字的索引的主要内容,如果未能解决你的问题,请参考以下文章

编写在数字数组中查找元素索引的函数[重复]

查找第二次出现索引最低的第一个重复元素

查找矩阵最小元素的 2 个或多个索引(行和列)

在Python中查找给定数组中最小值的索引

在javascript中的数组中查找多个最小值的索引

Python:在浮动列表中查找最小项目的索引[重复]