"Clonage" des vecteurs de ligne ou de colonne

155

Parfois, il est utile de "cloner" un vecteur ligne ou colonne dans une matrice. Par clonage, j'entends la conversion d'un vecteur de ligne tel que

[1,2,3]

Dans une matrice

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

ou un vecteur de colonne tel que

[1
 2
 3
]

dans

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

En matlab ou en octave, cela se fait assez facilement:

 x = [1,2,3]
 a = ones(3,1) * x
 a =

    1   2   3
    1   2   3
    1   2   3

 b = (x') * ones(1,3)
 b =

    1   1   1
    2   2   2
    3   3   3

Je veux répéter cela de manière numpy, mais sans succès

In [14]: x = array([1,2,3])
In [14]: ones((3,1)) * x
Out[14]:
array([[ 1.,  2.,  3.],
       [ 1.,  2.,  3.],
       [ 1.,  2.,  3.]])
# so far so good
In [16]: x.transpose() * ones((1,3))
Out[16]: array([[ 1.,  2.,  3.]])
# DAMN
# I end up with 
In [17]: (ones((3,1)) * x).transpose()
Out[17]:
array([[ 1.,  1.,  1.],
       [ 2.,  2.,  2.],
       [ 3.,  3.,  3.]])

Pourquoi la première méthode ( In [16]) ne fonctionnait-elle pas? Existe-t-il un moyen de réaliser cette tâche en python de manière plus élégante?

Boris Gorelik
la source
6
Dans Matlab, notez qu'il est beaucoup plus rapide à utiliser repmat: repmat([1 2 3],3,1)ourepmat([1 2 3].',1,3)
Luis Mendo
Octave a également repmat.
ma11hew28
Pour ceux qui cherchent à faire de même avec un dataframe pandas, consultez le tile_df lien ici
zelusp

Réponses:

80

Voici une façon élégante et pythonique de le faire:

>>> array([[1,2,3],]*3)
array([[1, 2, 3],
       [1, 2, 3],
       [1, 2, 3]])

>>> array([[1,2,3],]*3).transpose()
array([[1, 1, 1],
       [2, 2, 2],
       [3, 3, 3]])

le problème avec [16]semble être que la transposition n'a aucun effet sur un tableau. vous voulez probablement une matrice à la place:

>>> x = array([1,2,3])
>>> x
array([1, 2, 3])
>>> x.transpose()
array([1, 2, 3])
>>> matrix([1,2,3])
matrix([[1, 2, 3]])
>>> matrix([1,2,3]).transpose()
matrix([[1],
        [2],
        [3]])
Peter
la source
1
(La transposition fonctionne pour les tableaux 2D, par exemple pour le carré dans l'exemple, ou lors de la transformation en un (N,1)tableau de formes en utilisant .reshape(-1, 1))
Marquez
34
C'est très inefficace. Utilisez numpy.tilecomme indiqué dans la réponse de pv .
David Heffernan le
304

Utilisez numpy.tile:

>>> tile(array([1,2,3]), (3, 1))
array([[1, 2, 3],
       [1, 2, 3],
       [1, 2, 3]])

ou pour répéter des colonnes:

>>> tile(array([[1,2,3]]).transpose(), (1, 3))
array([[1, 1, 1],
       [2, 2, 2],
       [3, 3, 3]])
pv.
la source
16
Upvote! Sur mon système, pour un vecteur avec 10000 éléments répétés 1000 fois, la tileméthode est 19,5 fois plus rapide que la méthode de la réponse actuellement acceptée (en utilisant la méthode de l'opérateur de multiplication).
Dr.Jan-Philip Gehrcke
1
Dans la deuxième section ("colonnes répétées"), pouvez-vous expliquer ce que fait le deuxième ensemble de crochets, c'est-à-dire [[1,2,3]]
Ant
@Ant il se transforme en un tableau 2D avec la longueur 1 dans le premier axe (vertical sur votre écran) et la longueur 3 dans le deuxième axe (horizontal sur votre écran). La transposition lui fait alors avoir la longueur 3 dans le premier axe et la longueur 1 dans le deuxième axe. Une forme de tuile (1, 3)copie cette colonne plus de trois fois, c'est pourquoi les lignes du résultat contiennent chacune un seul élément distinct.
BallpointBen
Cela devrait être la réponse acceptée puisque vous pouvez passer n'importe quel vecteur déjà initialisé alors que celui accepté ne peut fonctionner que si vous ajoutez la virgule pendant que vous initialisez le vecteur. Merci !
Yohan Obadia le
Je ne peux pas faire fonctionner cela pour une solution
2D
42

Notez d'abord qu'avec les opérations de diffusion de numpy, il n'est généralement pas nécessaire de dupliquer des lignes et des colonnes. Voir ceci et cela pour les descriptions.

Mais pour ce faire, répéter et nouvel axe sont probablement le meilleur moyen

In [12]: x = array([1,2,3])

In [13]: repeat(x[:,newaxis], 3, 1)
Out[13]: 
array([[1, 1, 1],
       [2, 2, 2],
       [3, 3, 3]])

In [14]: repeat(x[newaxis,:], 3, 0)
Out[14]: 
array([[1, 2, 3],
       [1, 2, 3],
       [1, 2, 3]])

Cet exemple est pour un vecteur de ligne, mais appliquer cela à un vecteur de colonne est, espérons-le, évident. répéter semble bien épeler cela, mais vous pouvez également le faire par multiplication comme dans votre exemple

In [15]: x = array([[1, 2, 3]])  # note the double brackets

In [16]: (ones((3,1))*x).transpose()
Out[16]: 
array([[ 1.,  1.,  1.],
       [ 2.,  2.,  2.],
       [ 3.,  3.,  3.]])
tom10
la source
5
newaxis a l'avantage supplémentaire de ne pas copier les données tant qu'il n'en a pas besoin. Donc, si vous faites cela pour multiplier ou ajouter à un autre tableau 3x3, la répétition est inutile. Renseignez-vous sur la diffusion numpy pour avoir l'idée.
AFoglia
@AFoglia - Bon point. J'ai mis à jour ma réponse pour le souligner.
tom10
1
Quels avantages utiliser np.repeatvs np.tile?
mrgloom
@mrgloom: Aucun, surtout pour ce cas. Pour un petit tableau 1D, ils sont similaires et il n'y a pas de différence / avantage / avantage / etc. Personnellement, je trouve que la symétrie entre le clonage des lignes et des colonnes est plus intuitive, et je n'aime pas la transposition nécessaire pour la tuile, mais c'est juste une question de goût. La réponse de Mateen Ulhaq dit également que la répétition est plus rapide, mais cela peut dépendre du cas d'utilisation exact considéré, bien que la répétition soit beaucoup plus proche de la fonctionnalité C, donc restera probablement un peu plus rapide. En 2D, ils ont des comportements différents, c'est donc important.
tom10
12

Laisser:

>>> n = 1000
>>> x = np.arange(n)
>>> reps = 10000

Allocations sans frais

Une vue ne prend pas de mémoire supplémentaire. Ainsi, ces déclarations sont instantanées:

# New axis
x[np.newaxis, ...]

# Broadcast to specific shape
np.broadcast_to(x, (reps, n))

Allocation forcée

Si vous voulez forcer le contenu à résider en mémoire:

>>> %timeit np.array(np.broadcast_to(x, (reps, n)))
10.2 ms ± 62.3 µs per loop (mean ± std. dev. of 7 runs, 100 loops each)

>>> %timeit np.repeat(x[np.newaxis, :], reps, axis=0)
9.88 ms ± 52.4 µs per loop (mean ± std. dev. of 7 runs, 100 loops each)

>>> %timeit np.tile(x, (reps, 1))
9.97 ms ± 77.3 µs per loop (mean ± std. dev. of 7 runs, 100 loops each)

Les trois méthodes ont à peu près la même vitesse.

Calcul

>>> a = np.arange(reps * n).reshape(reps, n)
>>> x_tiled = np.tile(x, (reps, 1))

>>> %timeit np.broadcast_to(x, (reps, n)) * a
17.1 ms ± 284 µs per loop (mean ± std. dev. of 7 runs, 100 loops each)

>>> %timeit x[np.newaxis, :] * a
17.5 ms ± 300 µs per loop (mean ± std. dev. of 7 runs, 100 loops each)

>>> %timeit x_tiled * a
17.6 ms ± 240 µs per loop (mean ± std. dev. of 7 runs, 100 loops each)

Les trois méthodes ont à peu près la même vitesse.


Conclusion

Si vous souhaitez répliquer avant un calcul, envisagez d'utiliser l'une des méthodes «d'allocation à coût nul». Vous ne subirez pas la pénalité de performance de "l'allocation forcée".

Mateen Ulhaq
la source
8

Je pense que l'utilisation de la diffusion dans numpy est la meilleure et la plus rapide

J'ai fait une comparaison comme suit

import numpy as np
b = np.random.randn(1000)
In [105]: %timeit c = np.tile(b[:, newaxis], (1,100))
1000 loops, best of 3: 354 µs per loop

In [106]: %timeit c = np.repeat(b[:, newaxis], 100, axis=1)
1000 loops, best of 3: 347 µs per loop

In [107]: %timeit c = np.array([b,]*100).transpose()
100 loops, best of 3: 5.56 ms per loop

environ 15 fois plus rapide en utilisant la diffusion

smartkevin
la source
Vous pouvez indexer avec Nonepour faire la même chose.
DanielSank
qu'est-ce que newaxis?!
dreab
np.newaxis est un alias pour None
john ktejik
la répétition était plus rapide: 5,56 ms = 5560 µs
Augusto Fadel
4

Une solution propre consiste à utiliser la fonction de produit externe de NumPy avec un vecteur de uns:

np.outer(np.ones(n), x)

donne ndes lignes répétitives. Changez l'ordre des arguments pour obtenir des colonnes répétitives. Pour obtenir un nombre égal de lignes et de colonnes, vous pouvez faire

np.outer(np.ones_like(x), x)
Jon Deaton
la source
3

Vous pouvez utiliser

np.tile(x,3).reshape((4,3))

tile générera les répétitions du vecteur

et remodeler lui donnera la forme que vous voulez

thebeancounter
la source
1

Si vous avez un dataframe pandas et que vous souhaitez conserver les dtypes, même les catégoriques, c'est un moyen rapide de le faire:

import numpy as np
import pandas as pd
df = pd.DataFrame({1: [1, 2, 3], 2: [4, 5, 6]})
number_repeats = 50
new_df = df.reindex(np.tile(df.index, number_repeats))
Le chat impertinent
la source
-1
import numpy as np
x=np.array([1,2,3])
y=np.multiply(np.ones((len(x),len(x))),x).T
print(y)

donne:

[[ 1.  1.  1.]
 [ 2.  2.  2.]
 [ 3.  3.  3.]]
kibitzforu
la source