Vetores de linha ou coluna de "clonagem"

155

Às vezes, é útil "clonar" um vetor de linha ou coluna em uma matriz. Ao clonar, quero dizer converter um vetor de linha como

[1,2,3]

Em uma matriz

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

ou um vetor de coluna como

[1
 2
 3
]

para dentro

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

No matlab ou oitava, isso é feito com bastante facilidade:

 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

Quero repetir isso numpy, mas sem sucesso

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

Por que o primeiro método ( In [16]) não estava funcionando? Existe uma maneira de realizar essa tarefa em python de uma maneira mais elegante?

Boris Gorelik
fonte
6
No Matlab, observe que é muito mais rápido usar repmat: repmat([1 2 3],3,1)ourepmat([1 2 3].',1,3)
Luis Mendo
Oitava também tem repmat.
ma11hew28
Para aqueles que procuram fazer semelhante com pandas trama de dados de checkout o tile_df ligado aqui
zelusp

Respostas:

80

Aqui está uma maneira Pythonic elegante de fazer isso:

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

o problema com [16]parece ser que a transposição não tem efeito para uma matriz. você provavelmente está querendo uma matriz:

>>> 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
fonte
1
(transposta funciona para matrizes 2D, por exemplo, para a um quadrado, no exemplo, ou quando se transformar em uma (N,1)matriz usando -shape .reshape(-1, 1))
Mark
34
Isso é altamente ineficiente. Use numpy.tilecomo mostrado na resposta do pv .
David Heffernan
304

Use numpy.tile:

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

ou para repetir colunas:

>>> tile(array([[1,2,3]]).transpose(), (1, 3))
array([[1, 1, 1],
       [2, 2, 2],
       [3, 3, 3]])
pv.
fonte
16
Voto a favor! No meu sistema, para um vetor com 10000 elementos repetidos 1000 vezes, o tilemétodo é 19,5 vezes mais rápido que o método na resposta atualmente aceita (usando o método do operador de multiplicação).
Dr. Jan-Philip Gehrcke
1
Na segunda seção ("colunas repetidas"), você pode explicar o que o segundo conjunto de colchetes faz, ou seja, [[1,2,3]]
Ant
@Ant transforma em uma matriz 2D com comprimento 1 no primeiro eixo (vertical na tela) e comprimento 3 no segundo eixo (horizontal na tela). A transposição faz com que tenha comprimento 3 no primeiro eixo e comprimento 1 no segundo eixo. Uma forma de bloco (1, 3)copia esta coluna três vezes, e é por isso que as linhas do resultado contêm um único elemento distinto cada.
precisa
Essa deve ser a resposta aceita, pois você pode passar qualquer vetor já inicializado enquanto o aceito só funcionará se você adicionar a vírgula enquanto inicializa o vetor. Obrigado !
Yohan Obadia
Eu não posso chegar a este trabalho por um 2D para solução 3d :(
john ktejik
42

Primeiro, observe que nas operações de transmissão do numpy geralmente não é necessário duplicar linhas e colunas. Veja isto e isto para descrições.

Mas, para fazer isso, repetir e newaxis são provavelmente a melhor maneira

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

Este exemplo é para um vetor de linha, mas aplicar isso a um vetor de coluna é esperançosamente óbvio. repeat parece soletrar isso bem, mas você também pode fazê-lo através da multiplicação, como no seu exemplo

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
fonte
5
A newaxis tem o benefício adicional de não copiar os dados até que seja necessário. Portanto, se você estiver fazendo isso para multiplicar ou adicionar a outra matriz 3x3, a repetição é desnecessária. Leia a transmissão numpy para ter uma idéia.
AFoglia 12/10/09
@AFoglia - Bom ponto. Eu atualizei minha resposta para apontar isso.
tom10
1
Quais os benefícios de usar np.repeatvs np.tile?
Mrgloom
@ Mrgloom: Nenhum, principalmente, para este caso. Para uma matriz 1D pequena, eles são semelhantes e não há uma diferença / benefício / vantagem significativa / etc. Pessoalmente, acho a simetria entre a clonagem de linha e coluna mais intuitiva e não gosto da transposição necessária para o bloco, mas é apenas uma questão de gosto. A resposta de Mateen Ulhaq também diz que a repetição é mais rápida, mas isso pode depender do caso de uso exato considerado, embora a repetição esteja muito mais próxima da funcionalidade C, portanto provavelmente permanecerá um pouco mais rápida. Em 2D, eles têm comportamentos diferentes, por isso importa.
tom10
12

Deixei:

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

Alocações de custo zero

Uma visão não ocupa nenhuma memória adicional. Assim, essas declarações são instantâneas:

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

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

Alocação forçada

Se você deseja forçar o conteúdo a residir na memória:

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

Todos os três métodos têm aproximadamente a mesma velocidade.

Computação

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

Todos os três métodos têm aproximadamente a mesma velocidade.


Conclusão

Se você deseja replicar antes de um cálculo, considere usar um dos métodos "alocação de custo zero". Você não sofrerá a penalidade de desempenho da "alocação forçada".

Mateen Ulhaq
fonte
8

Eu acho que usar a transmissão numpy é o melhor e mais rápido

Eu fiz uma comparação da seguinte forma

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

cerca de 15 vezes mais rápido usando broadcast

smartkevin
fonte
Você pode indexar com Nonepara fazer a mesma coisa.
DanielSank
o que é newaxis ?!
dreab
np.newaxis é um pseudônimo para None
john ktejik 19/09/19
a repetição foi mais rápida: 5,56 ms = 5560 µs
Augusto Fadel
4

Uma solução limpa é usar a função de produto externo do NumPy com um vetor de uns:

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

nlinhas repetidas. Alterne a ordem dos argumentos para obter colunas repetidas. Para obter um número igual de linhas e colunas, você pode fazer

np.outer(np.ones_like(x), x)
Jon Deaton
fonte
3

Você pode usar

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

o bloco gerará os representantes do vetor

e remodelar dará a forma que você deseja

thebeancounter
fonte
1

Se você possui um dataframe de pandas e deseja preservar os tipos, mesmo os categóricos, é uma maneira rápida de fazê-lo:

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))
The Unfun Cat
fonte
-1
import numpy as np
x=np.array([1,2,3])
y=np.multiply(np.ones((len(x),len(x))),x).T
print(y)

rendimentos:

[[ 1.  1.  1.]
 [ 2.  2.  2.]
 [ 3.  3.  3.]]
kibitzforu
fonte