35 votes

Mélange des éléments non nuls de chaque ligne d'un tableau - Python / NumPy

J'ai un tableau qui est relativement rares, et je voudrais aller à travers chaque ligne et mélangez seulement la non-zéro éléments.

Exemple D'Entrée:

[2,3,1,0]
[0,0,2,1]

Exemple De Sortie:

[2,1,3,0]
[0,0,1,2]

Notez comment les zéros n'ont pas changé de position.

Mélanger tous les éléments dans chaque ligne (y compris zéro) je peux faire ceci:

for i in range(len(X)):
    np.random.shuffle(X[i, :])

Ce que j'ai essayé de faire est ceci:

for i in range(len(X)):
    np.random.shuffle(X[i, np.nonzero(X[i, :])])

Mais il n'a pas d'effet. J'ai remarqué que le type de retour d' X[i, np.nonzero(X[i, :])] est différent de X[i, :] qui pourrait être la cause.

In[30]: X[i, np.nonzero(X[i, :])]
Out[30]: array([[23,  5, 29, 11, 17]])

In[31]: X[i, :]
Out[31]: array([23,  5, 29, 11, 17])

15voto

randomir Points 12236

Vous pouvez utiliser le numpy.random.permutation non-inplace avec une indexation explicite autre que zéro:

 >>> X = np.array([[2,3,1,0], [0,0,2,1]])
>>> for i in range(len(X)):
...     idx = np.nonzero(X[i])
...     X[i][idx] = np.random.permutation(X[i][idx])
... 
>>> X
array([[3, 2, 1, 0],
       [0, 0, 2, 1]])
 

8voto

Daniel F Points 7247

Je pense avoir trouvé le trois-liner?

 i, j = np.nonzero(a.astype(bool))
k = np.argsort(i + np.random.rand(i.size))
a[i,j] = a[i,j[k]]
 

8voto

Divakar Points 20144

Comme promis, le quatrième jour de la bounty période, voici ma tentative de vectorisé solution. Les étapes sont expliquées en détail ci-dessous :

  • Pour plus de facilité, nous allons appeler le tableau d'entrée comme a. Générer des index uniques par ligne qui couvre la gamme de longueur de ligne. Pour cela, on peut simplement générer des nombres aléatoires de la même forme que le tableau d'entrée et obtenir l' argsort indices le long de chaque ligne, ce qui serait ces indices. Cette idée a été explorées en this post.

  • Index dans chaque ligne du tableau d'entrée avec ces indices comme les colonnes d'indices. Ainsi, nous aurions besoin d' advanced-indexing ici. Maintenant, ce qui nous donne un tableau avec chaque ligne étant mélangées. Appelons - b.

  • Depuis le déplacement est limité par ligne, si nous utilisons simplement la boolean-indexation : b[b!=0], on obtient la non-zéro les éléments qui y sont mélangées et aussi être restreint à des longueurs de non-zéros par ligne. C'est en raison du fait que les éléments dans un tableau NumPy sont stockés en ligne ordre majeur, donc avec boolean-indexation, il aurait choisi mélangées non nulle d'éléments de chaque ligne d'abord avant de passer à la ligne suivante. Encore une fois, si nous utilisons boolean-indexation de même pour a, c'est à dire a[a!=0], nous avons même eu le non nulle d'éléments de chaque ligne d'abord avant de passer à la ligne suivante et celles-ci seraient dans leur ordre d'origine. Donc, l'étape finale serait simplement de prendre masqué éléments b[b!=0] et attribuer dans le masqués endroits a[a!=0].

Ainsi, une mise en œuvre couvrant la mentionnés ci-dessus trois étapes seraient -

m,n = a.shape
rand_idx = np.random.rand(m,n).argsort(axis=1) #step1
b = a[np.arange(m)[:,None], rand_idx]          #step2  
a[a!=0] = b[b!=0]                              #step3 

Un exemple étape par étape, pourrait rendre les choses plus claires -

In [50]: a # Input array
Out[50]: 
array([[ 8,  5,  0, -4],
       [ 0,  6,  0,  3],
       [ 8,  5,  0, -4]])

In [51]: m,n = a.shape # Store shape information

# Unique indices per row that covers the range for row length
In [52]: rand_idx = np.random.rand(m,n).argsort(axis=1)

In [53]: rand_idx
Out[53]: 
array([[0, 2, 3, 1],
       [1, 0, 3, 2],
       [2, 3, 0, 1]])

# Get corresponding indexed array
In [54]: b = a[np.arange(m)[:,None], rand_idx]

# Do a check on the shuffling being restricted to per row
In [55]: a[a!=0]
Out[55]: array([ 8,  5, -4,  6,  3,  8,  5, -4])

In [56]: b[b!=0]
Out[56]: array([ 8, -4,  5,  6,  3, -4,  8,  5])

# Finally do the assignment based on masking on a and b
In [57]: a[a!=0] = b[b!=0]

In [58]: a # Final verification on desired result
Out[58]: 
array([[ 8, -4,  0,  5],
       [ 0,  6,  0,  3],
       [-4,  8,  0,  5]])

7voto

Divakar Points 20144

De référence pour la vectorisé solutions

Nous sommes à la recherche de la référence vectorisé solutions dans ce post. Maintenant, la vectorisation essaie d'éviter la boucle que nous allions parcourir chaque ligne et de faire le déplacement. Ainsi, le programme d'installation pour le tableau d'entrée comporte un plus grand nombre de lignes.

Approches -

def app1(a): # @Daniel F's soln
    i, j = np.nonzero(a.astype(bool))
    k = np.argsort(i + np.random.rand(i.size))
    a[i,j] = a[i,j[k]]
    return a

def app2(x): # @kazemakase's soln
    r, c = np.where(x != 0)
    n = c.size
    perm = np.random.permutation(n)
    i = np.argsort(perm + r * n)
    x[r, c] = x[r, c[i]]
    return x

def app3(a): # @Divakar's soln
    m,n = a.shape
    rand_idx = np.random.rand(m,n).argsort(axis=1)
    b = a[np.arange(m)[:,None], rand_idx]
    a[a!=0] = b[b!=0]
    return a

from scipy.ndimage.measurements import labeled_comprehension
def app4(a): # @FabienP's soln
    def func(array, idx):
        r[idx] = np.random.permutation(array)
        return True
    labels, idx = nz = a.nonzero()
    r = a[nz]
    labeled_comprehension(a[nz], labels + 1, np.unique(labels + 1),\
                                func, int, 0, pass_positions=True)
    a[nz] = r
    return a

L'analyse comparative de la procédure #1

Pour une juste analyse comparative, il semble raisonnable d'utiliser l'OP de l'échantillon et simplement la pile de ceux que plus de lignes pour obtenir un plus grand ensemble de données. Ainsi, avec cette configuration, nous pouvons créer deux cas, avec 2 millions de dollars et 20 millions de lignes de jeux de données.

Cas n ° 1 : Grand jeu de données avec 2*1000,000 lignes

In [174]: a = np.array([[2,3,1,0],[0,0,2,1]])

In [175]: a = np.vstack([a]*1000000)

In [176]: %timeit app1(a)
     ...: %timeit app2(a)
     ...: %timeit app3(a)
     ...: %timeit app4(a)
     ...: 
1 loop, best of 3: 264 ms per loop
1 loop, best of 3: 422 ms per loop
1 loop, best of 3: 254 ms per loop
1 loop, best of 3: 14.3 s per loop

Cas #2 : Plus grand ensemble de données avec 2*10,000,000 lignes

In [177]: a = np.array([[2,3,1,0],[0,0,2,1]])

In [178]: a = np.vstack([a]*10000000)

# app4 skipped here as it was slower on the previous smaller dataset
In [179]: %timeit app1(a)
     ...: %timeit app2(a)
     ...: %timeit app3(a)
     ...: 
1 loop, best of 3: 2.86 s per loop
1 loop, best of 3: 4.62 s per loop
1 loop, best of 3: 2.55 s per loop

L'analyse comparative de la procédure #2 : un Vaste

Pour couvrir tous les cas de la variable pourcentage de non-zéros dans le tableau d'entrée, nous sommes en couvrant une évaluation comparative des scénarios. Aussi, depuis app4 semblait beaucoup plus lente que les autres, pour un examen plus approfondi nous sauter ce dans cette section.

Fonction d'assistance à la configuration d'entrée de gamme :

def in_data(n_col, nnz_ratio):
    # max no. of elems that my system can handle, i.e. stretching it to limits.
    # The idea is to use this to decide the number of rows and always use
    # max. possible dataset size
    num_elem = 10000000

    n_row = num_elem//n_col
    a = np.zeros((n_row, n_col),dtype=int)
    L = int(round(a.size*nnz_ratio))
    a.ravel()[np.random.choice(a.size, L, replace=0)] = np.random.randint(1,6,L)
    return a

Chronométrage et le traçage de script (Utilise IPython fonctions magiques. Donc, doit être exécuté opon de copier et de coller sur console IPython) -

import matplotlib.pyplot as plt

# Setup input params
nnz_ratios = np.array([0.2, 0.4, 0.6, 0.8])
n_cols = np.array([4, 5, 8, 10, 15, 20, 25, 50])

init_arr1 = np.zeros((len(nnz_ratios), len(n_cols) ))
init_arr2 = np.zeros((len(nnz_ratios), len(n_cols) ))
init_arr3 = np.zeros((len(nnz_ratios), len(n_cols) ))

timings = {app1:init_arr1, app2:init_arr2, app3:init_arr3}
for i,nnz_ratio in enumerate(nnz_ratios):
    for j,n_col in enumerate(n_cols):
        a = in_data(n_col, nnz_ratio=nnz_ratio)
        for func in timings:
            res = %timeit -oq func(a)
            timings[func][i,j] = res.best
            print func.__name__, i, j, res.best

fig = plt.figure(1)
colors = ['b','k','r']
for i in range(len(nnz_ratios)):
    ax = plt.subplot(2,2,i+1)
    for f,func in enumerate(timings):
        ax.plot(n_cols, 
                [time for time in timings[func][i]], 
                label=str(func.__name__), color=colors[f])
    ax.set_xlabel('No. of cols')
    ax.set_ylabel('time [seconds]')
    ax.grid(which='both')
    ax.legend()
    plt.tight_layout()
    plt.title('Percentage non-zeros : '+str(int(100*nnz_ratios[i])) + '%')
plt.subplots_adjust(wspace=0.2, hspace=0.2)

Les Timings de la sortie -

enter image description here

Observations :

  • Approches #1, #2 n' argsort sur le non-zéro éléments à travers l'ensemble du tableau d'entrée. En tant que tel, il fonctionne mieux avec moins de pourcentage de non-zéros.

  • Approche n ° 3 crée des nombres aléatoires de la même forme que le tableau d'entrée, puis il reçoit argsort indices par ligne. Ainsi, pour un nombre donné de non-zéros dans l'entrée, les horaires sont plus raides-ish que les deux premières approches.

Conclusion :

Approche n ° 1 semble être assez bien jusqu'à 60% de non-zéro. Pour plus de non-zéros et si la ligne-les longueurs sont de petite taille, de l'approche n ° 3 semble exercer décemment.

4voto

FabienP Points 1574

Je suis venu avec que:

nz = a.nonzero()                      # Get nonzero indexes
a[nz] = np.random.permutation(a[nz])  # Shuffle nonzero values with mask

Qui à l'air plus simple (et un peu plus rapide?) que les autres solutions proposées.


EDIT: nouvelle version qui ne mélange pas les lignes

 labels, *idx = nz = a.nonzero()                                    # get masks
 a[nz] = np.concatenate([np.random.permutation(a[nz][labels == i])  # permute values
                         for i in np.unique(labels)])               # for each label

Où le premier tableau de l' a.nonzero() (indices de non valeurs zéro axis0) est utilisé comme étiquettes. C'est le truc qui ne mélange pas les lignes.

Ensuite, np.random.permutation est appliquée sur a[a.nonzero()] pour chaque "étiquette" de manière indépendante.

Soi-disant scipy.ndimage.measurements.labeled_comprehension peut être utilisé ici, on semble à l'échec avec np.random.permutation.

Et j'ai enfin vu qu'il ressemble beaucoup à ce que @randomir proposé. De toute façon, c'était juste pour relever le défi de la faire fonctionner.


EDIT2:

A finalement obtenu ce travail avec scipy.ndimage.measurements.labeled_comprehension

def shuffle_rows(a):
    def func(array, idx):
        r[idx] = np.random.permutation(array)
        return True
    labels, *idx = nz = a.nonzero()
    r = a[nz]
    labeled_comprehension(a[nz], labels + 1, np.unique(labels + 1), func, int, 0, pass_positions=True)
    a[nz] = r
    return a

Où:

  1. func() mélange les non valeurs zéro
  2. labeled_comprehension s'applique func() label-sage

Le présent document remplace la précédente boucle for et sera plus rapide sur les tableaux avec de nombreuses lignes.

Prograide.com

Prograide est une communauté de développeurs qui cherche à élargir la connaissance de la programmation au-delà de l'anglais.
Pour cela nous avons les plus grands doutes résolus en français et vous pouvez aussi poser vos propres questions ou résoudre celles des autres.

Powered by:

X