Numpy: 1d सरणी के तत्वों के सूचकांक को 2d सरणी के रूप में प्राप्त करें


10

मेरे पास इस तरह एक संख्यात्मक सरणी है: [1 2 2 0 0 1 3 5]

क्या तत्वों का सूचकांक 2d सरणी के रूप में प्राप्त करना संभव है? उदाहरण के लिए उपरोक्त इनपुट के लिए उत्तर होगा[[3 4], [0 5], [1 2], [6], [], [7]]

वर्तमान में मुझे अलग-अलग मूल्यों को लूप करना है और numpy.where(input == i)प्रत्येक मूल्य के लिए कॉल करना है, जिसमें एक बड़ा पर्याप्त इनपुट के साथ भयानक प्रदर्शन है।


np.argsort([1, 2, 2, 0, 0, 1, 3, 5])देता है array([3, 4, 0, 5, 1, 2, 6, 7], dtype=int64)। तो आप बस अगले तत्वों की तुलना कर सकते हैं।
vb_rises

जवाबों:


11

यहाँ एक ओ (अधिकतम (x) + लेन (x)) का उपयोग कर दृष्टिकोण है scipy.sparse:

import numpy as np
from scipy import sparse

x = np.array("1 2 2 0 0 1 3 5".split(),int)
x
# array([1, 2, 2, 0, 0, 1, 3, 5])


M,N = x.max()+1,x.size
sparse.csc_matrix((x,x,np.arange(N+1)),(M,N)).tolil().rows.tolist()
# [[3, 4], [0, 5], [1, 2], [6], [], [7]]

यह पदों पर प्रविष्टियों के साथ विरल मैट्रिक्स बनाकर काम करता है (x [0], 0), (x [1], 1), ... CSC(संपीड़ित विरल स्तंभ) प्रारूप का उपयोग करना यह सरल है। तब मैट्रिक्स को LIL(लिंक की गई सूची) प्रारूप में बदल दिया जाता है । यह प्रारूप प्रत्येक पंक्ति के कॉलम कॉलम को अपनी rowsविशेषता में एक सूची के रूप में संग्रहीत करता है , इसलिए हमें केवल इतना करना है कि इसे ले जाएं और इसे सूची में परिवर्तित करें।

ध्यान दें कि छोटे सरणियों के लिए argsortआधारित समाधान संभवतः तेज़ होते हैं लेकिन कुछ बड़े आकार के नहीं होते हैं।

संपादित करें:

argsort-बेड- numpyऑन सॉल्यूशन:

np.split(x.argsort(kind="stable"),np.bincount(x)[:-1].cumsum())
# [array([3, 4]), array([0, 5]), array([1, 2]), array([6]), array([], dtype=int64), array([7])]

यदि समूहों के भीतर सूचकांकों का क्रम मायने नहीं रखता है, तो आप भी कोशिश कर सकते हैं argpartition(यह इस छोटे उदाहरण में कोई फर्क नहीं पड़ता है लेकिन सामान्य रूप से इसकी गारंटी नहीं है):

bb = np.bincount(x)[:-1].cumsum()
np.split(x.argpartition(bb),bb)
# [array([3, 4]), array([0, 5]), array([1, 2]), array([6]), array([], dtype=int64), array([7])]

संपादित करें:

@Divakar के इस्तेमाल के खिलाफ सिफारिश की गई है np.split। इसके बजाय, एक लूप शायद तेज है:

A = x.argsort(kind="stable")
B = np.bincount(x+1).cumsum()
[A[B[i-1]:B[i]] for i in range(1,len(B))]

या आप बिल्कुल नए (Python3.8 +) वालरस ऑपरेटर का उपयोग कर सकते हैं:

A = x.argsort(kind="stable")
B = np.bincount(x)
L = 0
[A[L:(L:=L+b)] for b in B.tolist()]

संपादित करें (संपादित):

(शुद्ध सुन्न नहीं): सुंबा के विकल्प के रूप में (@ प्रेषक का पद देखें) हम पाइथ्रान का भी उपयोग कर सकते हैं।

संकलन pythran -O3 <filename.py>

import numpy as np

#pythran export sort_to_bins(int[:],int)

def sort_to_bins(idx, mx):
    if mx==-1: 
        mx = idx.max() + 1
    cnts = np.zeros(mx + 2, int)
    for i in range(idx.size):
        cnts[idx[i] + 2] += 1
    for i in range(3, cnts.size):
        cnts[i] += cnts[i-1]
    res = np.empty_like(idx)
    for i in range(idx.size):
        res[cnts[idx[i]+1]] = i
        cnts[idx[i]+1] += 1
    return [res[cnts[i]:cnts[i+1]] for i in range(mx)]

यहाँ numbaएक प्रदर्शन-वार द्वारा जीता जाता है:

repeat(lambda:enum_bins_numba_buffer(x),number=10)
# [0.6235917090671137, 0.6071486569708213, 0.6096088469494134]
repeat(lambda:sort_to_bins(x,-1),number=10)
# [0.6235359431011602, 0.6264424560358748, 0.6217901279451326]

पुराना सामान:

import numpy as np

#pythran export bincollect(int[:])

def bincollect(a):
    o = [[] for _ in range(a.max()+1)]
    for i,j in enumerate(a):
        o[j].append(i)
    return o

समय बनाम सुब्बा (पुराना)

timeit(lambda:bincollect(x),number=10)
# 3.5732191529823467
timeit(lambda:enumerate_bins(x),number=10)
# 6.7462647299980745

यह @ रैंडी के जवाब से थोड़ा तेज होने के साथ समाप्त हो गया
फ्रेडेरिको स्क्रैडॉन्ग

लूप-आधारित एक से बेहतर होना चाहिए np.split
दिवाकर 19

@ शिवकर अच्छे बिंदु, धन्यवाद!
पॉल पैंजर

8

आपके डेटा के आकार के आधार पर एक संभावित विकल्प सिर्फ बाहर छोड़ना numpyऔर उपयोग करना है collections.defaultdict:

In [248]: from collections import defaultdict

In [249]: d = defaultdict(list)

In [250]: l = np.random.randint(0, 100, 100000)

In [251]: %%timeit
     ...: for k, v in enumerate(l):
     ...:     d[v].append(k)
     ...:
10 loops, best of 3: 22.8 ms per loop

तब आप एक शब्दकोश के साथ समाप्त करते हैं {value1: [index1, index2, ...], value2: [index3, index4, ...]}। समय स्केलिंग सरणी के आकार के साथ रैखिक के काफी करीब है, इसलिए मेरी मशीन पर 10,000,000 ~ 2.7 का समय लगता है, जो उचित लगता है।


7

हालांकि अनुरोध एक numpyसमाधान के लिए है, मैंने यह देखने का फैसला किया कि क्या एक दिलचस्प- numbaआधारित समाधान है। और वास्तव में वहाँ है! यहां एक दृष्टिकोण है जो एक एकल प्रचारित बफर में संग्रहीत रैग्ड सरणी के रूप में विभाजित सूची का प्रतिनिधित्व करता है। यह पॉल पैंजरargsort द्वारा प्रस्तावित दृष्टिकोण से कुछ प्रेरणा लेता है । (पुराने संस्करण के लिए जो उतना अच्छा नहीं था, लेकिन सरल था, नीचे देखें।)

@numba.jit(numba.void(numba.int64[:], 
                      numba.int64[:], 
                      numba.int64[:]), 
           nopython=True)
def enum_bins_numba_buffer_inner(ints, bins, starts):
    for x in range(len(ints)):
        i = ints[x]
        bins[starts[i]] = x
        starts[i] += 1

@numba.jit(nopython=False)  # Not 100% sure this does anything...
def enum_bins_numba_buffer(ints):
    ends = np.bincount(ints).cumsum()
    starts = np.empty(ends.shape, dtype=np.int64)
    starts[1:] = ends[:-1]
    starts[0] = 0

    bins = np.empty(ints.shape, dtype=np.int64)
    enum_bins_numba_buffer_inner(ints, bins, starts)

    starts[1:] = ends[:-1]
    starts[0] = 0
    return [bins[s:e] for s, e in zip(starts, ends)]

यह 75 मिलियन में दस-मिलियन आइटम सूची की प्रक्रिया करता है, जो शुद्ध पायथन में लिखे गए सूची-आधारित संस्करण से लगभग 50x स्पीडअप है।

गतिशील रूप से आकार में "टाइप की गई सूचियों" के लिए हाल ही में जोड़े गए प्रायोगिक समर्थन के आधार पर एक धीमी लेकिन कुछ हद तक अधिक पठनीय संस्करण के लिए, जो हमें प्रत्येक बिन को एक आउट-ऑफ-ऑर्डर तरीके से अधिक तेज़ी से भरने की अनुमति देता है।

यह कुश्ती के numbaप्रकार के अनुमान इंजन के साथ थोड़ा सा है, और मुझे यकीन है कि उस हिस्से को संभालने का एक बेहतर तरीका है। यह भी ऊपर की तुलना में लगभग 10x धीमा निकला।

@numba.jit(nopython=True)
def enum_bins_numba(ints):
    bins = numba.typed.List()
    for i in range(ints.max() + 1):
        inner = numba.typed.List()
        inner.append(0)  # An awkward way of forcing type inference.
        inner.pop()
        bins.append(inner)

    for x, i in enumerate(ints):
        bins[i].append(x)

    return bins

मैंने निम्नलिखित के खिलाफ इनका परीक्षण किया:

def enum_bins_dict(ints):
    enum_bins = defaultdict(list)
    for k, v in enumerate(ints):
        enum_bins[v].append(k)
    return enum_bins

def enum_bins_list(ints):
    enum_bins = [[] for i in range(ints.max() + 1)]
    for x, i in enumerate(ints):
        enum_bins[i].append(x)
    return enum_bins

def enum_bins_sparse(ints):
    M, N = ints.max() + 1, ints.size
    return sparse.csc_matrix((ints, ints, np.arange(N + 1)),
                             (M, N)).tolil().rows.tolist()

मैंने उन्हें एक प्री-शेप्ड साइथन संस्करण के समान भी परीक्षण किया था enum_bins_numba_buffer(नीचे विस्तार से वर्णित है)।

दस लाख यादृच्छिक ints ( ints = np.random.randint(0, 100, 10000000)) की सूची में मुझे निम्नलिखित परिणाम प्राप्त हुए हैं:

enum_bins_dict(ints)
3.71 s ± 80.2 ms per loop (mean ± std. dev. of 7 runs, 1 loop each)

enum_bins_list(ints)
3.28 s ± 52.3 ms per loop (mean ± std. dev. of 7 runs, 1 loop each)

enum_bins_sparse(ints)
1.02 s ± 34.7 ms per loop (mean ± std. dev. of 7 runs, 1 loop each)

enum_bins_numba(ints)
693 ms ± 5.81 ms per loop (mean ± std. dev. of 7 runs, 1 loop each)

enum_bins_cython(ints)
82.3 ms ± 1.77 ms per loop (mean ± std. dev. of 7 runs, 10 loops each)

enum_bins_numba_buffer(ints)
77.4 ms ± 2.06 ms per loop (mean ± std. dev. of 7 runs, 10 loops each)

प्रभावशाली रूप से, numbaएक cythonही फ़ंक्शन के संस्करण के साथ काम करने का यह तरीका सीमा-जाँच के साथ भी बंद हो गया। मेरे पास अभी तक pythranइस प्रयोग का परीक्षण करने के लिए पर्याप्त परिचित नहीं है , लेकिन मैं एक तुलना देखने के लिए इच्छुक हूं। इस स्पीडअप के आधार पर संभावना है कि pythranइस दृष्टिकोण के साथ संस्करण भी थोड़ा तेज हो सकता है।

यहाँ cythonकुछ निर्माण निर्देशों के साथ, संदर्भ के लिए संस्करण है। आपके द्वारा cythonइंस्टॉल किए जाने के बाद, आपको setup.pyइस तरह की एक सरल फ़ाइल की आवश्यकता होगी :

from distutils.core import setup
from distutils.extension import Extension
from Cython.Build import cythonize
import numpy

ext_modules = [
    Extension(
        'enum_bins_cython',
        ['enum_bins_cython.pyx'],
    )
]

setup(
    ext_modules=cythonize(ext_modules),
    include_dirs=[numpy.get_include()]
)

और साइथन मॉड्यूल enum_bins_cython.pyx:

# cython: language_level=3

import cython
import numpy
cimport numpy

@cython.boundscheck(False)
@cython.cdivision(True)
@cython.wraparound(False)
cdef void enum_bins_inner(long[:] ints, long[:] bins, long[:] starts) nogil:
    cdef long i, x
    for x in range(len(ints)):
        i = ints[x]
        bins[starts[i]] = x
        starts[i] = starts[i] + 1

def enum_bins_cython(ints):
    assert (ints >= 0).all()
    # There might be a way to avoid storing two offset arrays and
    # save memory, but `enum_bins_inner` modifies the input, and
    # having separate lists of starts and ends is convenient for
    # the final partition stage.
    ends = numpy.bincount(ints).cumsum()
    starts = numpy.empty(ends.shape, dtype=numpy.int64)
    starts[1:] = ends[:-1]
    starts[0] = 0

    bins = numpy.empty(ints.shape, dtype=numpy.int64)
    enum_bins_inner(ints, bins, starts)

    starts[1:] = ends[:-1]
    starts[0] = 0
    return [bins[s:e] for s, e in zip(starts, ends)]

अपनी कार्यशील निर्देशिका में इन दो फ़ाइलों के साथ, यह कमांड चलाएँ:

python setup.py build_ext --inplace

फिर आप फ़ंक्शन का उपयोग करके आयात कर सकते हैं from enum_bins_cython import enum_bins_cython


मुझे आश्चर्य है कि यदि आप अजगर के बारे में जानते हैं जो बहुत व्यापक शब्दों में सुन्न के समान है। मैंने अपनी पोस्ट में एक पाइथन समाधान जोड़ा। इस अवसर पर पाइथरन को ऊपरी हाथ दिखाई देते हैं, जो बहुत तेजी से और अधिक पाइथोनिक समाधान प्रदान करता है।
पॉल पैंजर

@PaulPanzer दिलचस्प! मैंने इसके बारे में नहीं सुना था। मैं इकट्ठा करता हूं कि लिस्ट कोड स्थिर होने के बाद सुंबा देवों को अपेक्षित सिंटैक्टिक शुगर मिलाया जाएगा। यहां एक सुविधा / गति व्यापार बंद भी प्रतीत होता है - अलग-अलग पूर्व-निर्मित मॉड्यूल की आवश्यकता वाले दृष्टिकोण की तुलना में एक साधारण पायथन कोड बेस में एकीकृत करने के लिए जीट डेकोरेटर बहुत आसान है। लेकिन घिनौना दृष्टिकोण पर एक 3x स्पीडअप वास्तव में प्रभावशाली है, यहां तक ​​कि आश्चर्य की बात है!
7

बस याद है कि मैंने मूल रूप से इससे पहले किया था: stackoverflow.com/q/55226662/7207392 । क्या आप उस Q & A में अपने सुंबा और साइथन संस्करणों को जोड़ना चाहेंगे? केवल अंतर यह है: हम 0,1,2 सूचकांकों को बिन नहीं करते, ... लेकिन इसके बजाय एक और सरणी। और हम वास्तव में परिणामी सरणी को काटते हुए परेशान नहीं करते हैं।
पॉल पैंजर

@PaulPanzer आह बहुत मस्त। मैं इसे आज या कल किसी बिंदु पर जोड़ने की कोशिश करूंगा। क्या आप एक अलग उत्तर का सुझाव दे रहे हैं या सिर्फ अपने उत्तर को संपादित करें? किसी भी तरह खुश!
प्रेषक

महान! मुझे लगता है कि एक अलग पद बेहतर होगा लेकिन कोई मजबूत वरीयता नहीं।
पॉल पैंजर

6

यहाँ ऐसा करने के लिए वास्तव में अजीब तरीका है जो भयानक है, लेकिन मैंने इसे साझा नहीं करने के लिए बहुत मज़ेदार पाया - और सभी numpy!

out = np.array([''] * (x.max() + 1), dtype = object)
np.add.at(out, x, ["{} ".format(i) for i in range(x.size)])
[[int(i) for i in o.split()] for o in out]

Out[]:
[[3, 4], [0, 5], [1, 2], [6], [], [7]]

संपादित करें: यह सबसे अच्छा तरीका है जो मुझे इस रास्ते पर मिल सकता है। यह @PaulPanzer argsortसमाधान की तुलना में अभी भी 10 गुना धीमा है :

out = np.empty((x.max() + 1), dtype = object)
out[:] = [[]] * (x.max() + 1)
coords = np.empty(x.size, dtype = object)
coords[:] = [[i] for i in range(x.size)]
np.add.at(out, x, coords)
list(out)

2

आप इसे संख्याओं का शब्दकोश बनाकर कर सकते हैं, कुंजियाँ संख्याएँ होंगी और मान उन सूचकांकों के होने चाहिए जो संख्या देखी गई है, यह इसे करने के सबसे तेज़ तरीकों में से एक है, आप कोड bellow देख सकते हैं:

>>> import numpy as np
>>> a = np.array([1 ,2 ,2 ,0 ,0 ,1 ,3, 5])
>>> b = {}
# Creating an empty list for the numbers that exist in array a
>>> for i in range(np.min(a),np.max(a)+1):
    b[str(i)] = []

# Adding indices to the corresponding key
>>> for i in range(len(a)):
    b[str(a[i])].append(i)

# Resulting Dictionary
>>> b
{'0': [3, 4], '1': [0, 5], '2': [1, 2], '3': [6], '4': [], '5': [7]}

# Printing the result in the way you wanted.
>>> for i in sorted (b.keys()) :
     print(b[i], end = " ")

[3, 4] [0, 5] [1, 2] [6] [] [7] 

1

स्यूडोकोड:

  1. "2d सरणी में 1d सरणियों की संख्या" प्राप्त करें, अधिकतम मूल्य से अपने संख्यात्मक सरणी के न्यूनतम मूल्य को घटाकर और फिर प्लस एक। आपके मामले में, यह 5-0 + 1 = 6 होगा

  2. इसके भीतर 1d सरणियों की संख्या के साथ 2d सरणी को इनिशियलाइज़ करें। अपने मामले में, 6d 1d सरणी के साथ 2d सरणी को इनिशियलाइज़ करें। प्रत्येक 1d सरणी आपके संख्यात्मक सरणी में एक अद्वितीय तत्व से मेल खाती है, उदाहरण के लिए, पहला 1d सरणी '0' के अनुरूप होगा, दूसरा 1d सरणी '1' के अनुरूप होगा, ...

  3. अपने सुन्न सरणी के माध्यम से लूप, तत्व के सूचकांक को सही 1d सरणी में रखें। आपके मामले में, आपके खसरे सरणी में पहले तत्व का सूचकांक दूसरे 1d सरणी में रखा जाएगा, आपके खस्ता सरणी में दूसरे तत्व का सूचकांक तीसरे 1d सरणी में रखा जाएगा, ....

यह स्यूडोकोड को चलने में रैखिक समय लगेगा क्योंकि यह आपके खस्ता सरणी की लंबाई पर निर्भर करता है।


1

यह आपको वही देता है जो आप चाहते हैं और मेरी मशीन पर 10,000,000 के लिए लगभग 2.5 सेकंड लेंगे:

import numpy as np
import timeit

# x = np.array("1 2 2 0 0 1 3 5".split(),int)
x = np.random.randint(0, 100, 100000)

def create_index_list(x):
    d = {}
    max_value = -1
    for i,v in enumerate(x):
        if v > max_value:
            max_value = v
        try:
            d[v].append(i)
        except:
            d[v] = [i]
    result_list = []
    for i in range(max_value+1):
        if i in d:
            result_list.append(d[i])
        else:
            result_list.append([])
    return result_list

# print(create_index_list(x))
print(timeit.timeit(stmt='create_index_list(x)', number=1, globals=globals()))

0

तो तत्वों की एक सूची दी गई है, आप (तत्व, सूचकांक) जोड़े बनाना चाहते हैं। रैखिक समय में, इसे निम्न प्रकार से किया जा सकता है:

hashtable = dict()
for idx, val in enumerate(mylist):
    if val not in hashtable.keys():
         hashtable[val] = list()
    hashtable[val].append(idx)
newlist = sorted(hashtable.values())

यह O (n) समय लेना चाहिए। मैं अब के रूप में एक तेज समाधान के बारे में नहीं सोच सकता, लेकिन अगर मैं करता हूं तो यहां अपडेट करूंगा।

हमारी साइट का प्रयोग करके, आप स्वीकार करते हैं कि आपने हमारी Cookie Policy और निजता नीति को पढ़ और समझा लिया है।
Licensed under cc by-sa 3.0 with attribution required.