Numpy: 1d dizisinin elemanlarının dizinini 2d dizi olarak al


10

Böyle bir numpy dizi var: [1 2 2 0 0 1 3 5]

Elemanların indeksini 2d dizisi olarak almak mümkün müdür? Örneğin, yukarıdaki girdinin cevabı[[3 4], [0 5], [1 2], [6], [], [7]]

Şu anda farklı değerleri döngü ve numpy.where(input == i)yeterince büyük bir girdi ile korkunç performansa sahip her bir değer için çağrı var.


np.argsort([1, 2, 2, 0, 0, 1, 3, 5])verir array([3, 4, 0, 5, 1, 2, 6, 7], dtype=int64). yalnızca sonraki öğeleri karşılaştırabilirsiniz.
vb_rises

Yanıtlar:


11

İşte O (max (x) + len (x)) yaklaşımını kullanarak 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]]

Bu, (x [0], 0), (x [1], 1), ... konumlarındaki girişleri içeren seyrek bir matris oluşturarak çalışır. CSC(Sıkıştırılmış seyrek sütun) formatını kullanmak oldukça basittir. Matris daha sonra LIL(bağlantılı liste) formatına dönüştürülür. Bu biçim, her satırın sütun indekslerini rowsözniteliğinde bir liste olarak saklar , bu yüzden tek yapmamız gereken bunu almak ve listeye dönüştürmektir.

Küçük dizilere argsortdayalı çözümlerin muhtemelen daha hızlı olduğunu, ancak bazı büyük olmayan boyutlarda bunun geçeceğini unutmayın.

DÜZENLE:

argsort-bazlı numpy-sadece çözüm:

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])]

Gruplardaki endekslerin sırası önemli değilse, deneyebilirsiniz argpartition(bu küçük örnekte fark yaratmaz, ancak bu genel olarak garanti edilmez):

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])]

DÜZENLE:

@Divakar kullanılmamasını önerir np.split. Bunun yerine, bir döngü muhtemelen daha hızlıdır:

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

Veya yeni (Python3.8 +) mors operatörünü kullanabilirsiniz:

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

DÜZENLEME (düzenlendi):

(Saf numpy değil): Numba'ya bir alternatif olarak (bkz. @ Senderle'ın gönderisine) pythran da kullanabiliriz.

Şununla derleyin: 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)]

İşte numbaperformans açısından bir bıyık ile kazanır:

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]

Daha eski şeyler:

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

Zamanlamalar - numba (eski)

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

Bu, @ Randy'nin cevabından biraz daha hızlı oldu
Frederico Schardong

Döngü tabanlı olandan daha iyi olmalıdır np.split.
Divakar

@Divakar iyi bir nokta, teşekkürler!
Paul Panzer

8

Verilerinizin boyutuna bağlı olarak olası bir seçenek, yalnızca okulu bırakmak numpyve kullanmaktır 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

Sonra bir sözlük ile sonuçlanır {value1: [index1, index2, ...], value2: [index3, index4, ...]}. Zaman ölçeklendirme, dizinin boyutu ile doğrusal olana oldukça yakındır, bu yüzden 10.000.000 makinemde ~ 2.7 saniye alır, bu da yeterince makul görünüyor.


7

Her ne kadar bir numpyçözüm talebi olsa da , ilginç bir çözüm olup olmadığına karar verdim numba. Ve gerçekten de var! Bölümlenmiş listeyi, önceden yerleştirilmiş tek bir arabellekte depolanan düzensiz bir dizi olarak temsil eden bir yaklaşım. Bu Paul Panzerargsort tarafından önerilen yaklaşımdan ilham alıyor . (Aynı zamanda yapmayan, ancak daha basit olan eski bir sürüm için aşağıya bakın.)

@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)]

Bu, 75ms'de on milyonluk bir öğe listesini işler; bu, saf Python'da yazılmış liste tabanlı bir sürümden yaklaşık 50 kat daha hızlıdır.

Daha yavaş ama biraz daha okunabilir bir sürüm için, daha önce sahip olduğum şey, dinamik olarak boyutlandırılmış "tür listeler" için son zamanlarda eklenen deneysel desteğe dayanarak, her bölmeyi çok daha hızlı bir şekilde sıra dışı bir şekilde doldurmamıza izin veriyor.

Bu numbabiraz 'tür çıkarsama motoru' ile boğuşuyor ve eminim bu kısmı ele almanın daha iyi bir yolu var. Bu aynı zamanda yukarıdakilerden neredeyse 10 kat daha yavaş olduğu ortaya çıkıyor.

@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

Bunları aşağıdakilere karşı test ettim:

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()

Ayrıca, benzer bir derlenmiş cython sürümüne karşı test ettim enum_bins_numba_buffer(aşağıda ayrıntılı olarak açıklanmıştır).

On milyon rastgele ints ( ints = np.random.randint(0, 100, 10000000)) listesinde aşağıdaki sonuçları alıyorum:

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)

Etkileyici bir şekilde, bu şekilde çalışma , sınır denetimi kapalı olsa bile aynı işlevin numbabir cythonversiyonundan daha iyi performans gösterir . Henüz pythranbu yaklaşımı kullanarak test etmek için yeterince bilgim yok , ancak bir karşılaştırma görmek isterim. Muhtemelen bu hızlandırmaya dayanarakpythran sürümün bu yaklaşımla biraz daha hızlı olabileceği düşünülmektedir.

İşte cythonbazı derleme talimatları ile birlikte referans için versiyon. Yükledikten sonra cython, bunun setup.pygibi basit bir dosyaya ihtiyacınız olacak :

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()]
)

Ve cython modülü 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)]

Çalışma dizininizdeki bu iki dosyayla şu komutu çalıştırın:

python setup.py build_ext --inplace

Ardından işlevi kullanarak içe aktarabilirsiniz from enum_bins_cython import enum_bins_cython.


Acaba çok geniş anlamda numbaya benzeyen piran'ın farkında olup olmadığınızı merak ediyorum. Yazıma bir pythran çözümü ekledim. Bu vesileyle pythran, daha hızlı ve çok daha pythonic bir çözüm sunan üst ele sahip gibi görünecektir.
Paul Panzer

@PaulPanzer ilginç! Bunu duymamıştım. Liste kodu sabit olduğunda numba geliştiricilerin beklenen sözdizimsel şekeri ekleyeceğini düşünüyorum. Ayrıca burada bir kolaylık / hız değiş tokuşu var gibi görünüyor - jit dekoratörünün, önceden derlenmiş ayrı modüller gerektiren bir yaklaşıma kıyasla sıradan bir Python kod tabanına entegre edilmesi çok kolaydır. Ama scipy yaklaşımı üzerinde 3 kat bir hız gerçekten etkileyici, hatta şaşırtıcı!
senderle

Sadece bunu daha önce yaptığımı hatırladım: stackoverflow.com/q/55226662/7207392 . Bu soru ve cevaplara numba ve cython sürümlerinizi eklemek ister misiniz? Tek fark şudur: 0,1,2 indekslerini değil, başka bir diziyi. Ve sonuçta ortaya çıkan diziyi kesmeye zahmet etmiyoruz.
Paul Panzer

@ PaulPanzer ah çok havalı. Bugün veya yarın bir noktada eklemeye çalışacağım. Ayrı bir yanıt mı yoksa yalnızca yanıtınızda bir düzenleme mi öneriyorsunuz? Her iki şekilde de mutlu!
senderle

Harika! Ayrı bir gönderinin daha iyi olacağını, ancak güçlü bir tercih olmadığını düşünüyorum.
Paul Panzer

6

İşte bunu yapmak için gerçekten garip bir yol, ama paylaşmak için çok komik buldum - ve hepsi 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]]

EDIT: Bu yol boyunca bulabildiğim en iyi yöntem budur. @PaulPanzer'in çözümünden hala 10 kat daha yavaş argsort:

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

Bir sayı sözlüğü yaparak bunu yapabilirsiniz, tuşlar sayılar olacak ve değerler görülen endeksler olmalı, bu bunu yapmanın en hızlı yollarından biri, aşağıdaki kodu görebilirsiniz:

>>> 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

pseudocode:

  1. "2d dizisindeki 1d dizilerinin sayısı" nı, numpy dizinizin minimum değerini maksimum değerden ve sonra artı bir değerden çıkararak alın. Sizin durumunuzda, 5-0 + 1 = 6 olacak

  2. içinde 1d dizisi olan bir 2d dizisi başlatın. Sizin durumunuzda, içinde 6 1d dizisi olan bir 2d dizisi başlatın. Her 1d dizisi, numpy dizinizdeki benzersiz bir öğeye karşılık gelir, örneğin, ilk 1d dizisi '0'a karşılık gelir, ikinci 1d dizisi' 1'e karşılık gelir, ...

  3. numpy dizinizi kullanarak döngü yapın, öğenin dizinini karşılık gelen 1d dizisine yerleştirin. Sizin durumunuzda, numpy dizinizdeki ilk öğenin dizini ikinci 1d dizisine, numpy dizinizdeki ikinci öğenin dizini üçüncü 1d dizisine konur, ....

Bu sözde kod, numpy dizininizin uzunluğuna bağlı olduğu için çalıştırılması doğrusal zaman alacaktır.


1

Bu size tam olarak ne istediğinizi verir ve makinemde 10.000.000 için yaklaşık 2,5 saniye sürer:

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

Öyleyse elemanların bir listesi verildiğinde, (element, index) çiftleri yapmak istersiniz. Doğrusal zamanda, bu şu şekilde yapılabilir:

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())

Bu O (n) zaman almalıdır. Şu andan itibaren daha hızlı bir çözüm düşünemiyorum, ama eğer burada güncellenir.

Sitemizi kullandığınızda şunları okuyup anladığınızı kabul etmiş olursunuz: Çerez Politikası ve Gizlilik Politikası.
Licensed under cc by-sa 3.0 with attribution required.