numpy: frekuensi paling efisien dihitung untuk nilai unik dalam sebuah array

244

Di numpy/ scipy, apakah ada cara yang efisien untuk mendapatkan jumlah frekuensi untuk nilai unik dalam array?

Sesuatu di sepanjang garis ini:

x = array( [1,1,1,2,2,2,5,25,1,1] )
y = freq_count( x )
print y

>> [[1, 5], [2,3], [5,1], [25,1]]

(Untuk Anda, pengguna R di luar sana, pada dasarnya saya mencari table()fungsi)

Abe
sumber
5
Apakah collections.Counter(x)cukup?
pylang
1
Akan lebih baik saya pikir jika Anda mencentang sekarang jawaban ini sebagai benar untuk pertanyaan Anda: stackoverflow.com/a/25943480/9024698 .
Diasingkan
Collections.counter sangat lambat. Lihat posting saya: stackoverflow.com/questions/41594940/…
Sembei Norimaki

Jawaban:

161

Lihatlah np.bincount:

http://docs.scipy.org/doc/numpy/reference/generated/numpy.bincount.html

import numpy as np
x = np.array([1,1,1,2,2,2,5,25,1,1])
y = np.bincount(x)
ii = np.nonzero(y)[0]

Lalu:

zip(ii,y[ii]) 
# [(1, 5), (2, 3), (5, 1), (25, 1)]

atau:

np.vstack((ii,y[ii])).T
# array([[ 1,  5],
         [ 2,  3],
         [ 5,  1],
         [25,  1]])

atau bagaimanapun Anda ingin menggabungkan jumlah dan nilai unik.

JoshAdel
sumber
42
Hai, Ini tidak akan berfungsi jika elemen x memiliki dtype selain int.
Manoj
7
Ini tidak akan berfungsi jika mereka bukan int bukan negatif, dan akan sangat tidak efisien jika int ditempatkan.
Erik
Dengan numpy versi 1.10 saya menemukan bahwa, untuk menghitung integer, ini sekitar 6 kali lebih cepat daripada np.unique. Juga, perhatikan bahwa ia juga menghitung int negatif, jika parameter yang benar diberikan.
Jihun
@ Manoj: Elemen saya x adalah array. Saya menguji solusi jme.
Catalina Chircu
508

Pada Numpy 1.9, metode termudah dan tercepat adalah dengan hanya menggunakan numpy.unique, yang sekarang memiliki return_countsargumen kata kunci:

import numpy as np

x = np.array([1,1,1,2,2,2,5,25,1,1])
unique, counts = np.unique(x, return_counts=True)

print np.asarray((unique, counts)).T

Pemberian yang mana:

 [[ 1  5]
  [ 2  3]
  [ 5  1]
  [25  1]]

Perbandingan cepat dengan scipy.stats.itemfreq:

In [4]: x = np.random.random_integers(0,100,1e6)

In [5]: %timeit unique, counts = np.unique(x, return_counts=True)
10 loops, best of 3: 31.5 ms per loop

In [6]: %timeit scipy.stats.itemfreq(x)
10 loops, best of 3: 170 ms per loop
jme
sumber
22
Terima kasih telah memperbarui! Ini sekarang, IMO, jawaban yang benar.
Erve1879
1
BAM! inilah mengapa kami memperbarui ... ketika kami menemukan jawaban seperti ini. Panjang sekali numpy 1.8. Bagaimana kita bisa mendapatkan ini ke atas daftar?
user1269942
Jika Anda mendapatkan kesalahan: TypeError: unique () mendapat argumen kata kunci tak terduga 'return_counts', lakukan saja: unik, counts = np.unique (x, True)
NumesSanguis
3
@NumesSanguis Versi numpy apa yang Anda gunakan? Sebelum v1.9, return_countsargumen kata kunci tidak ada, yang mungkin menjelaskan pengecualian. Dalam hal ini, dokumen menyarankan yang np.unique(x, True)setara dengan np.unique(x, return_index=True), yang tidak mengembalikan jumlah.
jme
1
Dalam versi numpy yang lebih tua idiom khas untuk mendapatkan hal yang sama adalah unique, idx = np.unique(x, return_inverse=True); counts = np.bincount(idx). Ketika fitur ini ditambahkan (lihat di sini ) beberapa pengujian informal menggunakan return_countsclocking lebih dari 5x lebih cepat.
Jaime
133

Pembaruan: Metode yang disebutkan dalam jawaban asli sudah usang, kita harus menggunakan cara baru sebagai gantinya:

>>> import numpy as np
>>> x = [1,1,1,2,2,2,5,25,1,1]
>>> np.array(np.unique(x, return_counts=True)).T
    array([[ 1,  5],
           [ 2,  3],
           [ 5,  1],
           [25,  1]])

Jawaban asli:

Anda dapat menggunakan scipy.stats.itemfreq

>>> from scipy.stats import itemfreq
>>> x = [1,1,1,2,2,2,5,25,1,1]
>>> itemfreq(x)
/usr/local/bin/python:1: DeprecationWarning: `itemfreq` is deprecated! `itemfreq` is deprecated and will be removed in a future version. Use instead `np.unique(..., return_counts=True)`
array([[  1.,   5.],
       [  2.,   3.],
       [  5.,   1.],
       [ 25.,   1.]])
McKelvin
sumber
1
Sepertinya pendekatan yang paling pythonic sejauh ini. Juga, saya mengalami masalah dengan "objek terlalu dalam untuk array yang diinginkan" masalah dengan np.bincount pada 100k x 100k matriks.
metasequoia
1
Saya lebih suka menyarankan pertanyaan awal untuk mengubah jawaban terangkum dari yang pertama ke yang ini, untuk meningkatkan visibilitasnya
wiswit
Ini lambat untuk versi sebelum 0.14.
Jason S
perhatikan bahwa jika array penuh dengan string, kedua elemen di setiap item yang dikembalikan adalah string juga.
user1269942
Sepertinya itemfreq telah ditinggalkan
Terence Parr
48

Saya juga tertarik dengan ini, jadi saya melakukan sedikit perbandingan kinerja (menggunakan perfplot , proyek kesayangan saya). Hasil:

y = np.bincount(a)
ii = np.nonzero(y)[0]
out = np.vstack((ii, y[ii])).T

sejauh ini yang tercepat. (Perhatikan skala log.)

masukkan deskripsi gambar di sini


Kode untuk menghasilkan plot:

import numpy as np
import pandas as pd
import perfplot
from scipy.stats import itemfreq


def bincount(a):
    y = np.bincount(a)
    ii = np.nonzero(y)[0]
    return np.vstack((ii, y[ii])).T


def unique(a):
    unique, counts = np.unique(a, return_counts=True)
    return np.asarray((unique, counts)).T


def unique_count(a):
    unique, inverse = np.unique(a, return_inverse=True)
    count = np.zeros(len(unique), np.int)
    np.add.at(count, inverse, 1)
    return np.vstack((unique, count)).T


def pandas_value_counts(a):
    out = pd.value_counts(pd.Series(a))
    out.sort_index(inplace=True)
    out = np.stack([out.keys().values, out.values]).T
    return out


perfplot.show(
    setup=lambda n: np.random.randint(0, 1000, n),
    kernels=[bincount, unique, itemfreq, unique_count, pandas_value_counts],
    n_range=[2 ** k for k in range(26)],
    logx=True,
    logy=True,
    xlabel="len(a)",
)
Nico Schlömer
sumber
1
Terima kasih telah memposting kode untuk menghasilkan plot. Tidak tahu tentang perfplot sebelumnya. Tampak berguna.
ruffsl
Saya dapat menjalankan kode Anda dengan menambahkan opsi equality_check=array_sorteqdi perfplot.show(). Apa yang menyebabkan kesalahan (dalam Python 2) adalah pd.value_counts(bahkan dengan sort = False).
user2314737
33

Menggunakan modul panda:

>>> import pandas as pd
>>> import numpy as np
>>> x = np.array([1,1,1,2,2,2,5,25,1,1])
>>> pd.value_counts(x)
1     5
2     3
25    1
5     1
dtype: int64
ivankeller
sumber
5
pd.Series () tidak perlu. Kalau tidak, contoh yang bagus. Numpy juga. Panda dapat mengambil daftar sederhana sebagai masukan.
Yohan Obadia
1
@YohanObadia - tergantung pada ukuran array, pertama mengubahnya menjadi seri telah membuat operasi akhir lebih cepat untuk saya. Saya akan menebak sekitar 50.000 nilai.
n1k31t4
1
Saya mengedit jawaban saya untuk memperhitungkan komentar yang relevan dari @YohanObadia
ivankeller
19

Sejauh ini ini adalah solusi yang paling umum dan performan; terkejut belum diposting.

import numpy as np

def unique_count(a):
    unique, inverse = np.unique(a, return_inverse=True)
    count = np.zeros(len(unique), np.int)
    np.add.at(count, inverse, 1)
    return np.vstack(( unique, count)).T

print unique_count(np.random.randint(-10,10,100))

Berbeda dengan jawaban yang saat ini diterima, ia bekerja pada semua tipe data yang dapat diurutkan (bukan hanya int positif), dan memiliki kinerja optimal; satu-satunya biaya yang signifikan adalah penyortiran yang dilakukan oleh np.unique.

Eelco Hoogendoorn
sumber
tidak berfungsi:AttributeError: 'numpy.ufunc' object has no attribute 'at'
PR
Metode yang lebih sederhana adalah memanggilnp.bincount(inverse)
ali_m
15

numpy.bincountmungkin merupakan pilihan terbaik. Jika array Anda mengandung sesuatu selain bilangan bulat kecil, mungkin berguna untuk membungkusnya seperti ini:

def count_unique(keys):
    uniq_keys = np.unique(keys)
    bins = uniq_keys.searchsorted(keys)
    return uniq_keys, np.bincount(bins)

Sebagai contoh:

>>> x = array([1,1,1,2,2,2,5,25,1,1])
>>> count_unique(x)
(array([ 1,  2,  5, 25]), array([5, 3, 1, 1]))
Bi Rico
sumber
8

Meskipun sudah dijawab, saya menyarankan pendekatan berbeda yang memanfaatkan numpy.histogram. Fungsi yang diberi urutan ini mengembalikan frekuensi elemen-elemennya yang dikelompokkan dalam nampan .

Namun waspadalah : ini berfungsi dalam contoh ini karena angka adalah bilangan bulat. Jika mereka di mana bilangan real, maka solusi ini tidak akan berlaku juga.

>>> from numpy import histogram
>>> y = histogram (x, bins=x.max()-1)
>>> y
(array([5, 3, 0, 0, 1, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0,
       1]),
 array([  1.,   2.,   3.,   4.,   5.,   6.,   7.,   8.,   9.,  10.,  11.,
        12.,  13.,  14.,  15.,  16.,  17.,  18.,  19.,  20.,  21.,  22.,
        23.,  24.,  25.]))
Jir
sumber
5
import pandas as pd
import numpy as np
x = np.array( [1,1,1,2,2,2,5,25,1,1] )
print(dict(pd.Series(x).value_counts()))

Ini memberi Anda: {1: 5, 2: 3, 5: 1, 25: 1}

Kerem T
sumber
1
collections.Counter(x)juga memberikan hasil yang sama. Saya percaya OP menginginkan output yang menyerupai tablefungsi R. Menjaga Seriesmungkin lebih bermanfaat.
pylang
Harap dicatat bahwa transfer akan diperlukan pd.Series(x).reshape(-1)jika array multidimensi.
natsuapo
4

Untuk menghitung non-integer unik - mirip dengan jawaban Eelco Hoogendoorn tetapi jauh lebih cepat (faktor 5 pada mesin saya), saya biasa weave.inlinemenggabungkan numpy.uniquedengan sedikit kode-c;

import numpy as np
from scipy import weave

def count_unique(datain):
  """
  Similar to numpy.unique function for returning unique members of
  data, but also returns their counts
  """
  data = np.sort(datain)
  uniq = np.unique(data)
  nums = np.zeros(uniq.shape, dtype='int')

  code="""
  int i,count,j;
  j=0;
  count=0;
  for(i=1; i<Ndata[0]; i++){
      count++;
      if(data(i) > data(i-1)){
          nums(j) = count;
          count = 0;
          j++;
      }
  }
  // Handle last value
  nums(j) = count+1;
  """
  weave.inline(code,
      ['data', 'nums'],
      extra_compile_args=['-O2'],
      type_converters=weave.converters.blitz)
  return uniq, nums

Info profil

> %timeit count_unique(data)
> 10000 loops, best of 3: 55.1 µs per loop

numpyVersi murni Eelco :

> %timeit unique_count(data)
> 1000 loops, best of 3: 284 µs per loop

Catatan

Ada redundansi di sini ( uniquemelakukan pengurutan juga), yang berarti bahwa kode mungkin dapat lebih dioptimalkan dengan meletakkan uniquefungsi di dalam loop kode-c.

Jmetz
sumber
4

Pertanyaan lama, tetapi saya ingin memberikan solusi saya sendiri yang ternyata menjadi yang tercepat, gunakan normal listbukan np.arraysebagai input (atau transfer ke daftar terlebih dahulu), berdasarkan tes bangku saya.

Lihat itu jika Anda menemukannya juga.

def count(a):
    results = {}
    for x in a:
        if x not in results:
            results[x] = 1
        else:
            results[x] += 1
    return results

Sebagai contoh,

>>>timeit count([1,1,1,2,2,2,5,25,1,1]) would return:

100000 loop, terbaik 3: 2,26 μs per loop

>>>timeit count(np.array([1,1,1,2,2,2,5,25,1,1]))

100000 loop, terbaik 3: 8,8 μs per loop

>>>timeit count(np.array([1,1,1,2,2,2,5,25,1,1]).tolist())

100000 loop, terbaik 3: 5,85 μs per loop

Sementara jawaban yang diterima akan lebih lambat, dan scipy.stats.itemfreqsolusinya bahkan lebih buruk.


Pengujian yang lebih mendalam tidak mengkonfirmasi ekspektasi yang dirumuskan.

from zmq import Stopwatch
aZmqSTOPWATCH = Stopwatch()

aDataSETasARRAY = ( 100 * abs( np.random.randn( 150000 ) ) ).astype( np.int )
aDataSETasLIST  = aDataSETasARRAY.tolist()

import numba
@numba.jit
def numba_bincount( anObject ):
    np.bincount(    anObject )
    return

aZmqSTOPWATCH.start();np.bincount(    aDataSETasARRAY );aZmqSTOPWATCH.stop()
14328L

aZmqSTOPWATCH.start();numba_bincount( aDataSETasARRAY );aZmqSTOPWATCH.stop()
592L

aZmqSTOPWATCH.start();count(          aDataSETasLIST  );aZmqSTOPWATCH.stop()
148609L

Ref. komentar di bawah tentang cache dan efek samping dalam-RAM lainnya yang memengaruhi dataset kecil hasil pengujian berulang secara besar-besaran.

Rain Lee
sumber
Jawaban ini benar-benar baik, karena ini menunjukkan numpybelum tentu cara untuk pergi.
Mahdi
@Rain Lee menarik. Sudahkah Anda memvalidasi silang daftar hipotesis juga pada beberapa ukuran dataset yang tidak dapat di-cache? Mari kita asumsikan 150.000 item acak dalam representasi mana pun dan diukur sedikit lebih akurat pada sekali proses seperti contoh dari aZmqStopwatch.start (); count (aRepresentation); aZmqStopwatch.stop () ?
user3666197
Melakukan beberapa pengujian dan ya, ada perbedaan besar dalam kinerja dataset nyata. Pengujian membutuhkan sedikit lebih banyak wawasan tentang mekanika internal python daripada menjalankan hanya loop skala kasar dan mengutip nanoseconds in-vitro yang tidak realistis . Seperti yang diuji - np.bincount () dapat dibuat untuk menangani 150.000 array dalam waktu kurang dari 600 [kita] sementara hitungan def -ed di atas () pada representasi daftar pra-konversi daripadanya membutuhkan lebih dari 122.000 [kita]
user3666197
Ya, aturan praktis saya adalah numpy untuk apa pun yang dapat menangani sejumlah kecil latensi tetapi memiliki potensi untuk menjadi sangat besar, daftar untuk kumpulan data yang lebih kecil di mana latensi kritis, dan tentu saja pembandingan nyata FTW :)
David
1

beberapa hal seperti ini harus dilakukan:

#create 100 random numbers
arr = numpy.random.random_integers(0,50,100)

#create a dictionary of the unique values
d = dict([(i,0) for i in numpy.unique(arr)])
for number in arr:
    d[j]+=1   #increment when that value is found

Juga, posting sebelumnya tentang penghitungan elemen unik ini secara efisien tampaknya sangat mirip dengan pertanyaan Anda, kecuali saya kehilangan sesuatu.

benjaminmgross
sumber
Pertanyaan yang ditautkan agak mirip, tetapi sepertinya dia bekerja dengan tipe data yang lebih rumit.
Abe
1

penghitungan frekuensi multi-dimensi, yaitu penghitungan array.

>>> print(color_array    )
  array([[255, 128, 128],
   [255, 128, 128],
   [255, 128, 128],
   ...,
   [255, 128, 128],
   [255, 128, 128],
   [255, 128, 128]], dtype=uint8)


>>> np.unique(color_array,return_counts=True,axis=0)
  (array([[ 60, 151, 161],
    [ 60, 155, 162],
    [ 60, 159, 163],
    [ 61, 143, 162],
    [ 61, 147, 162],
    [ 61, 162, 163],
    [ 62, 166, 164],
    [ 63, 137, 162],
    [ 63, 169, 164],
   array([     1,      2,      2,      1,      4,      1,      1,      2,
         3,      1,      1,      1,      2,      5,      2,      2,
       898,      1,      1,  
vishal
sumber
1
import pandas as pd
import numpy as np

print(pd.Series(name_of_array).value_counts())
RAJAT BHATHEJA
sumber
0
from collections import Counter
x = array( [1,1,1,2,2,2,5,25,1,1] )
mode = counter.most_common(1)[0][0]
伍宜昌
sumber