Capítulo 4. Cálculo numérico con NumPy
Este trabajo se ha traducido utilizando IA. Agradecemos tus opiniones y comentarios: translation-feedback@oreilly.com
Los ordenadores son inútiles. Sólo pueden dar respuestas.
Pablo Picasso
Aunque el propio intérprete de Python ya trae consigo una rica variedad de estructuras de datos, NumPy
y otras bibliotecas las complementan de forma valiosa. Este capítulo de se centra en NumPy
, que proporciona un objeto array multidimensional para almacenar arrays de datos homogéneos o heterogéneos y admite la vectorización del código.
El capítulo cubre las siguientes estructuras de datos:
Tipo de objeto | Significado | Se utiliza para |
---|---|---|
|
objeto matrizn-dimensional |
Grandes matrices de datos numéricos |
|
Objeto matriz bidimensional |
Datos tabulares organizados en columnas |
Este capítulo está organizado como sigue:
- "Matrices de datos"
-
Esta sección trata sobre el manejo de matrices de datos con código Python puro.
- "Matrices NumPy regulares"
-
Esta es la sección principal sobre la clase
NumPy
ndarray
regular, el caballo de batalla en casi todos los casos de uso intensivo de datos en Python que implican datos numéricos. - "Matrices NumPy estructuradas"
-
Esta breve sección presenta los objetos estructurados (o de registro)
ndarray
para el manejo de datos tabulares con columnas. - "Vectorización del código"
-
En esta sección se trata la vectorización del código y sus ventajas; también se analiza la importancia de la disposición de la memoria en determinados escenarios.
Matrices de datos
El capítulo anterior mostró que Python proporciona algunas estructuras de datos generales bastante útiles y flexibles. En particular, los objetos list
pueden considerarse un verdadero caballo de batalla con muchas características y áreas de aplicación convenientes. Utilizar una estructura de datos tan flexible (mutable) tiene un coste, en forma de un uso de memoria relativamente alto, un rendimiento más lento, o ambas cosas. Sin embargo, las aplicaciones científicas y financieras suelen necesitar operaciones de alto rendimiento con estructuras de datos especiales. Una de las estructuras de datos más importantes en este sentido es la matriz. Las matrices suelen estructurar otros objetos (fundamentales) del mismo tipo de datos en filas y columnas.
Supongamos por el momento que sólo son relevantes los números, aunque el concepto se generaliza también a otros tipos de datos. En el caso más sencillo, una matriz unidimensional representa entonces, matemáticamente hablando, un vector de, en general, números reales, representados internamente por objetos float
. Consta entonces de una sola fila o columna de elementos únicamente. En el caso más común, una matriz representa una matriz i × j de elementos. Este concepto se generaliza a cubos de elementos i × j × k en tres dimensiones, así como a matrices generales de n dimensiones de forma .
Las disciplinas matemáticas como el álgebra lineal y la teoría de los espacios vectoriales ilustran que dichas estructuras matemáticas son de gran importancia en varias disciplinas y campos científicos. Por tanto, puede resultar fructífero disponer de una clase especializada de estructuras de datos diseñadas explícitamente para manejar matrices de forma cómoda y eficiente. Aquí es donde entra en juego la biblioteca de Python NumPy
, con su potente clase ndarray
. Antes de presentar esta clase en el siguiente apartado, esta sección ilustra dos alternativas para el manejo de matrices.
Matrices con listas en Python
Las matrices pueden construirse con las estructuras de datos incorporadas presentadas en el capítulo anterior. Los objetos list
son especialmente adecuados para realizar esta tarea. Un simple list
ya puede considerarse una matriz unidimensional:
In
[
1
]
:
v
=
[
0.5
,
0.75
,
1.0
,
1.5
,
2.0
]
Puesto que los objetos list
pueden contener otros objetos arbitrarios, también pueden contener otros objetos list
. De este modo, las matrices bidimensionales y de mayor dimensión se construyen fácilmente anidando objetos list
:
In
[
2
]
:
m
=
[
v
,
v
,
v
]
m
Out
[
2
]
:
[
[
0.5
,
0.75
,
1.0
,
1.5
,
2.0
]
,
[
0.5
,
0.75
,
1.0
,
1.5
,
2.0
]
,
[
0.5
,
0.75
,
1.0
,
1.5
,
2.0
]
]
También se pueden seleccionar fácilmente filas mediante indexación simple o elementos individuales mediante indexación doble (en cambio, las columnas enteras no son tan fáciles de seleccionar):
In
[
3
]:
m
[
1
]
Out
[
3
]:
[
0.5
,
0.75
,
1.0
,
1.5
,
2.0
]
In
[
4
]:
m
[
1
][
0
]
Out
[
4
]:
0.5
La anidación puede ampliarse aún más para obtener estructuras más generales:
In
[
5
]
:
v1
=
[
0.5
,
1.5
]
v2
=
[
1
,
2
]
m
=
[
v1
,
v2
]
c
=
[
m
,
m
]
c
Out
[
5
]
:
[
[
[
0.5
,
1.5
]
,
[
1
,
2
]
]
,
[
[
0.5
,
1.5
]
,
[
1
,
2
]
]
]
In
[
6
]
:
c
[
1
]
[
1
]
[
0
]
Out
[
6
]
:
1
Ten en cuenta que la combinación de objetos de la forma que acabamos de presentar suele funcionar con punteros de referencia a los objetos originales. ¿Qué significa esto en la práctica? Echa un vistazo a las siguientes operaciones:
In
[
7
]:
v
=
[
0.5
,
0.75
,
1.0
,
1.5
,
2.0
]
m
=
[
v
,
v
,
v
]
m
Out
[
7
]:
[[
0.5
,
0.75
,
1.0
,
1.5
,
2.0
],
[
0.5
,
0.75
,
1.0
,
1.5
,
2.0
],
[
0.5
,
0.75
,
1.0
,
1.5
,
2.0
]]
Cambia ahora el valor del primer elemento del objeto v
y observa qué ocurre con el objeto m
:
In
[
8
]:
v
[
0
]
=
'Python'
m
Out
[
8
]:
[[
'Python'
,
0.75
,
1.0
,
1.5
,
2.0
],
[
'Python'
,
0.75
,
1.0
,
1.5
,
2.0
],
[
'Python'
,
0.75
,
1.0
,
1.5
,
2.0
]]
Esto puede evitarse utilizando la función deepcopy()
del módulo copy
:
In
[
9
]
:
from
copy
import
deepcopy
v
=
[
0.5
,
0.75
,
1.0
,
1.5
,
2.0
]
m
=
3
*
[
deepcopy
(
v
)
,
]
m
Out
[
9
]
:
[
[
0.5
,
0.75
,
1.0
,
1.5
,
2.0
]
,
[
0.5
,
0.75
,
1.0
,
1.5
,
2.0
]
,
[
0.5
,
0.75
,
1.0
,
1.5
,
2.0
]
]
In
[
10
]
:
v
[
0
]
=
'
Python
'
m
Out
[
10
]
:
[
[
0.5
,
0.75
,
1.0
,
1.5
,
2.0
]
,
[
0.5
,
0.75
,
1.0
,
1.5
,
2.0
]
,
[
0.5
,
0.75
,
1.0
,
1.5
,
2.0
]
]
La clase array de Python
Existe es un módulo dedicado array
disponible en Python. Según la documentación
Este módulo define un tipo de objeto que puede representar de forma compacta una matriz de valores básicos: caracteres, enteros, números de coma flotante. Las matrices son tipos de secuencia y se comportan de forma muy parecida a las listas, salvo que el tipo de los objetos almacenados en ellas está restringido. El tipo se especifica en el momento de crear el objeto mediante un código de tipo, que es un único carácter.
Considera el siguiente código, que crea un objeto array
a partir de un objeto list
:
In
[
11
]
:
v
=
[
0.5
,
0.75
,
1.0
,
1.5
,
2.0
]
In
[
12
]
:
import
array
In
[
13
]
:
a
=
array
.
array
(
'
f
'
,
v
)
a
Out
[
13
]
:
array
(
'
f
'
,
[
0.5
,
0.75
,
1.0
,
1.5
,
2.0
]
)
In
[
14
]
:
a
.
append
(
0.5
)
a
Out
[
14
]
:
array
(
'
f
'
,
[
0.5
,
0.75
,
1.0
,
1.5
,
2.0
,
0.5
]
)
In
[
15
]
:
a
.
extend
(
[
5.0
,
6.75
]
)
a
Out
[
15
]
:
array
(
'
f
'
,
[
0.5
,
0.75
,
1.0
,
1.5
,
2.0
,
0.5
,
5.0
,
6.75
]
)
In
[
16
]
:
2
*
a
Out
[
16
]
:
array
(
'
f
'
,
[
0.5
,
0.75
,
1.0
,
1.5
,
2.0
,
0.5
,
5.0
,
6.75
,
0.5
,
0.75
,
1.0
,
1.5
,
2.0
,
0.5
,
5.0
,
6.75
]
)
La instanciación del objeto
array
confloat
como código de tipo.Los métodos principales funcionan de forma similar a los del objeto
list
.Aunque la "multiplicación escalar" funciona en principio, el resultado no es el matemáticamente esperado, sino que los elementos se repiten.
Si se intenta añadir un objeto de un tipo de datos distinto del especificado, se produce un error TypeError
:
In
[
17
]
:
a
.
append
(
'
string
'
)
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
TypeErrorTraceback
(
most
recent
call
last
)
<
ipython
-
input
-
17
-
14
cd6281866b
>
in
<
module
>
(
)
-
-
-
-
>
1
a
.
append
(
'
string
'
)
TypeError
:
must
be
real
number
,
not
str
In
[
18
]
:
a
.
tolist
(
)
Out
[
18
]
:
[
0.5
,
0.75
,
1.0
,
1.5
,
2.0
,
0.5
,
5.0
,
6.75
]
Sólo se pueden añadir objetos
float
; otros tipos de datos/códigos de tipo provocan errores.Sin embargo, el objeto
array
puede volver a convertirse fácilmente en un objetolist
si se necesita esa flexibilidad.
Una ventaja de la clase array
es que tiene incorporada la función de almacenamiento y recuperación:
In
[
19
]
:
f
=
open
(
'
array.apy
'
,
'
wb
'
)
a
.
tofile
(
f
)
f
.
close
(
)
In
[
20
]
:
with
open
(
'
array.apy
'
,
'
wb
'
)
as
f
:
a
.
tofile
(
f
)
In
[
21
]
:
!
ls
-
n
arr
*
-
rw
-
r
-
-
r
-
-
@
1
503
20
32
Nov
7
11
:
46
array
.
apy
Abre un archivo en el disco para escribir datos binarios.
Escribe los datos de
array
en el archivo.Cierra el archivo.
Alternativa: utiliza un contexto
with
para la misma operación.Muestra el archivo tal y como está escrito en el disco.
Como antes, el tipo de datos del objeto array
es importante a la hora de leer los datos del disco:
In
[
22
]
:
b
=
array
.
array
(
'
f
'
)
In
[
23
]
:
with
open
(
'
array.apy
'
,
'
rb
'
)
as
f
:
b
.
fromfile
(
f
,
5
)
In
[
24
]
:
b
Out
[
24
]
:
array
(
'
f
'
,
[
0.5
,
0.75
,
1.0
,
1.5
,
2.0
]
)
In
[
25
]
:
b
=
array
.
array
(
'
d
'
)
In
[
26
]
:
with
open
(
'
array.apy
'
,
'
rb
'
)
as
f
:
b
.
fromfile
(
f
,
2
)
In
[
27
]
:
b
Out
[
27
]
:
array
(
'
d
'
,
[
0.0004882813645963324
,
0.12500002956949174
]
)
Instantiza un nuevo objeto
array
con código de tipofloat
.Abre el archivo para leer datos binarios ...
... y lee cinco elementos en el objeto
b
.Instantiza un nuevo objeto
array
con código de tipodouble
.Lee dos elementos del archivo.
La diferencia en los códigos de tipo da lugar a números "erróneos".
Matrices NumPy regulares
Componer estructuras de matrices con objetos list
funciona, en cierto modo. Pero no es realmente cómodo, y la clase list
no se ha creado con este objetivo específico. Su ámbito de aplicación es mucho más amplio y general. La clase array
es un poco más especializada, ya que proporciona algunas funciones útiles para trabajar con matrices de datos. Sin embargo, una clase verdaderamente especializada podría ser realmente beneficiosa para manejar estructuras de tipo matriz.
Lo básico
numpy.ndarray
es una clase de este tipo, creada con el objetivo específico de manejar matrices n-dimensionales de forma cómoda y eficaz, es decir, con un alto rendimiento. La mejor forma de ilustrar el manejo básico de las instancias de esta clase es mediante ejemplos:
In
[
28
]
:
import
numpy
as
np
In
[
29
]
:
a
=
np
.
array
(
[
0
,
0.5
,
1.0
,
1.5
,
2.0
]
)
a
Out
[
29
]
:
array
(
[
0.
,
0.5
,
1.
,
1.5
,
2.
]
)
In
[
30
]
:
type
(
a
)
Out
[
30
]
:
numpy
.
ndarray
In
[
31
]
:
a
=
np
.
array
(
[
'
a
'
,
'
b
'
,
'
c
'
]
)
a
Out
[
31
]
:
array
(
[
'
a
'
,
'
b
'
,
'
c
'
]
,
dtype
=
'
<U1
'
)
In
[
32
]
:
a
=
np
.
arange
(
2
,
20
,
2
)
a
Out
[
32
]
:
array
(
[
2
,
4
,
6
,
8
,
10
,
12
,
14
,
16
,
18
]
)
In
[
33
]
:
a
=
np
.
arange
(
8
,
dtype
=
np
.
float
)
a
Out
[
33
]
:
array
(
[
0.
,
1.
,
2.
,
3.
,
4.
,
5.
,
6.
,
7.
]
)
In
[
34
]
:
a
[
5
:
]
Out
[
34
]
:
array
(
[
5.
,
6.
,
7.
]
)
In
[
35
]
:
a
[
:
2
]
Out
[
35
]
:
array
(
[
0.
,
1.
]
)
Importa el paquete
numpy
.Crea un objeto
ndarray
a partir de un objetolist
confloat
s.Crea un objeto
ndarray
a partir de un objetolist
constr
s.np.arange()
funciona de forma similar arange()
...... pero toma como entrada adicional el parámetro
dtype
.Con objetos unidimensionales
ndarray
, la indexación funciona como de costumbre.
Una característica importante de la clase ndarray
es la multitud de métodos incorporados. Por ejemplo
In
[
36
]
:
a
.
sum
(
)
Out
[
36
]
:
28.0
In
[
37
]
:
a
.
std
(
)
Out
[
37
]
:
2.29128784747792
In
[
38
]
:
a
.
cumsum
(
)
Out
[
38
]
:
array
(
[
0.
,
1.
,
3.
,
6.
,
10.
,
15.
,
21.
,
28.
]
)
La suma de todos los elementos.
La desviación típica de los elementos.
La suma acumulada de todos los elementos (empezando en la posición 0 del índice).
Otra característica importante de son las operaciones matemáticas (vectorizadas) definidas sobre ndarray
objetos:
In
[
39
]
:
l
=
[
0.
,
0.5
,
1.5
,
3.
,
5.
]
2
*
l
Out
[
39
]
:
[
0.0
,
0.5
,
1.5
,
3.0
,
5.0
,
0.0
,
0.5
,
1.5
,
3.0
,
5.0
]
In
[
40
]
:
a
Out
[
40
]
:
array
(
[
0.
,
1.
,
2.
,
3.
,
4.
,
5.
,
6.
,
7.
]
)
In
[
41
]
:
2
*
a
Out
[
41
]
:
array
(
[
0.
,
2.
,
4.
,
6.
,
8.
,
10.
,
12.
,
14.
]
)
In
[
42
]
:
a
*
*
2
Out
[
42
]
:
array
(
[
0.
,
1.
,
4.
,
9.
,
16.
,
25.
,
36.
,
49.
]
)
In
[
43
]
:
2
*
*
a
Out
[
43
]
:
array
(
[
1.
,
2.
,
4.
,
8.
,
16.
,
32.
,
64.
,
128.
]
)
In
[
44
]
:
a
*
*
a
Out
[
44
]
:
array
(
[
1.00000e+00
,
1.00000e+00
,
4.00000e+00
,
2.70000e+01
,
2.56000e+02
,
3.12500e+03
,
4.66560e+04
,
8.23543e+05
]
)
La multiplicación escalar con objetos
list
da lugar a una repetición de elementos.Por el contrario, el trabajo con objetos
ndarray
implementa una multiplicación escalar adecuada.Calcula los valores cuadrados elemento a elemento.
Esto interpreta los elementos del
ndarray
como los poderes.Calcula la potencia de cada elemento respecto a sí mismo.
Las funciones universales son otra característica importante del paquete NumPy
. Son "universales" en el sentido de que, en general, operan tanto sobre objetos ndarray
como sobre tipos de datos básicos de Python. Sin embargo, al aplicar funciones universales a, por ejemplo, un objeto Python float
, hay que tener en cuenta la reducción del rendimiento en comparación con la misma funcionalidad que se encuentra en el módulo math
:
In
[
45
]
:
np
.
exp
(
a
)
Out
[
45
]
:
array
(
[
1.00000000e+00
,
2.71828183e+00
,
7.38905610e+00
,
2.00855369e+01
,
5.45981500e+01
,
1.48413159e+02
,
4.03428793e+02
,
1.09663316e+03
]
)
In
[
46
]
:
np
.
sqrt
(
a
)
Out
[
46
]
:
array
(
[
0.
,
1.
,
1.41421356
,
1.73205081
,
2.
,
2.23606798
,
2.44948974
,
2.64575131
]
)
In
[
47
]
:
np
.
sqrt
(
2.5
)
Out
[
47
]
:
1.5811388300841898
In
[
48
]
:
import
math
In
[
49
]
:
math
.
sqrt
(
2.5
)
Out
[
49
]
:
1.5811388300841898
In
[
50
]
:
math
.
sqrt
(
a
)
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
TypeErrorTraceback
(
most
recent
call
last
)
<
ipython
-
input
-
50
-
b39de4150838
>
in
<
module
>
(
)
-
-
-
-
>
1
math
.
sqrt
(
a
)
TypeError
:
only
size
-
1
arrays
can
be
converted
to
Python
scalars
In
[
51
]
:
%
timeit
np
.
sqrt
(
2.5
)
722
ns
±
13.7
ns
per
loop
(
mean
±
std
.
dev
.
of
7
runs
,
1000000
loops
each
)
In
[
52
]
:
%
timeit
math
.
sqrt
(
2.5
)
91.8
ns
±
4.13
ns
per
loop
(
mean
±
std
.
dev
.
of
7
runs
,
10000000
loops
each
)
Calcula los valores exponenciales por elementos.
Calcula la raíz cuadrada de cada elemento.
Calcula la raíz cuadrada de un objeto Python
float
.El mismo cálculo, esta vez utilizando el módulo
math
.La función
math.sqrt()
no puede aplicarse directamente al objetondarray
.Aplicación de la función universal
np.sqrt()
a un objeto Pythonfloat
...... es mucho más lento que la misma operación con la función
math.sqrt()
.
Dimensiones múltiples
La transición de a más de una dimensión es perfecta, y todas las características presentadas hasta ahora se trasladan a los casos más generales. En particular, el sistema de indexación es coherente en todas las dimensiones:
In
[
53
]
:
b
=
np
.
array
(
[
a
,
a
*
2
]
)
b
Out
[
53
]
:
array
(
[
[
0.
,
1.
,
2.
,
3.
,
4.
,
5.
,
6.
,
7.
]
,
[
0.
,
2.
,
4.
,
6.
,
8.
,
10.
,
12.
,
14.
]
]
)
In
[
54
]
:
b
[
0
]
Out
[
54
]
:
array
(
[
0.
,
1.
,
2.
,
3.
,
4.
,
5.
,
6.
,
7.
]
)
In
[
55
]
:
b
[
0
,
2
]
Out
[
55
]
:
2.0
In
[
56
]
:
b
[
:
,
1
]
Out
[
56
]
:
array
(
[
1.
,
2.
]
)
In
[
57
]
:
b
.
sum
(
)
Out
[
57
]
:
84.0
In
[
58
]
:
b
.
sum
(
axis
=
0
)
Out
[
58
]
:
array
(
[
0.
,
3.
,
6.
,
9.
,
12.
,
15.
,
18.
,
21.
]
)
In
[
59
]
:
b
.
sum
(
axis
=
1
)
Out
[
59
]
:
array
(
[
28.
,
56.
]
)
Construye un objeto bidimensional
ndarray
a partir del unidimensional.Selecciona la primera fila.
Selecciona el tercer elemento de la primera fila; los índices están separados, dentro de los corchetes, por una coma.
Selecciona la segunda columna.
Calcula la suma de todos los valores.
Calcula la suma a lo largo del primer eje; es decir, por columnas.
Calcula la suma a lo largo del segundo eje; es decir, por filas.
Hay varias formas de inicializar (instanciar) objetos ndarray
. Una es la presentada anteriormente, a través de np.array
. Sin embargo, esto supone que todos los elementos de la matriz ya están disponibles. Por el contrario, uno puede desear que los objetos ndarray
se instancien primero para rellenarlos después con los resultados generados durante la ejecución del código. Para ello, puedes utilizar las siguientes funciones:
In
[
60
]
:
c
=
np
.
zeros
(
(
2
,
3
)
,
dtype
=
'
i
'
,
order
=
'
C
'
)
c
Out
[
60
]
:
array
(
[
[
0
,
0
,
0
]
,
[
0
,
0
,
0
]
]
,
dtype
=
int32
)
In
[
61
]
:
c
=
np
.
ones
(
(
2
,
3
,
4
)
,
dtype
=
'
i
'
,
order
=
'
C
'
)
c
Out
[
61
]
:
array
(
[
[
[
1
,
1
,
1
,
1
]
,
[
1
,
1
,
1
,
1
]
,
[
1
,
1
,
1
,
1
]
]
,
[
[
1
,
1
,
1
,
1
]
,
[
1
,
1
,
1
,
1
]
,
[
1
,
1
,
1
,
1
]
]
]
,
dtype
=
int32
)
In
[
62
]
:
d
=
np
.
zeros_like
(
c
,
dtype
=
'
f16
'
,
order
=
'
C
'
)
d
Out
[
62
]
:
array
(
[
[
[
0.
,
0.
,
0.
,
0.
]
,
[
0.
,
0.
,
0.
,
0.
]
,
[
0.
,
0.
,
0.
,
0.
]
]
,
[
[
0.
,
0.
,
0.
,
0.
]
,
[
0.
,
0.
,
0.
,
0.
]
,
[
0.
,
0.
,
0.
,
0.
]
]
]
,
dtype
=
float128
)
In
[
63
]
:
d
=
np
.
ones_like
(
c
,
dtype
=
'
f16
'
,
order
=
'
C
'
)
d
Out
[
63
]
:
array
(
[
[
[
1.
,
1.
,
1.
,
1.
]
,
[
1.
,
1.
,
1.
,
1.
]
,
[
1.
,
1.
,
1.
,
1.
]
]
,
[
[
1.
,
1.
,
1.
,
1.
]
,
[
1.
,
1.
,
1.
,
1.
]
,
[
1.
,
1.
,
1.
,
1.
]
]
]
,
dtype
=
float128
)
In
[
64
]
:
e
=
np
.
empty
(
(
2
,
3
,
2
)
)
e
Out
[
64
]
:
array
(
[
[
[
0.00000000e+000
,
0.00000000e+000
]
,
[
0.00000000e+000
,
0.00000000e+000
]
,
[
0.00000000e+000
,
0.00000000e+000
]
]
,
[
[
0.00000000e+000
,
0.00000000e+000
]
,
[
0.00000000e+000
,
7.49874326e+247
]
,
[
1.28822975e-231
,
4.33190018e-311
]
]
]
)
In
[
65
]
:
f
=
np
.
empty_like
(
c
)
f
Out
[
65
]
:
array
(
[
[
[
0
,
0
,
0
,
0
]
,
[
0
,
0
,
0
,
0
]
,
[
0
,
0
,
0
,
0
]
]
,
[
[
0
,
0
,
0
,
0
]
,
[
0
,
0
,
740455269
,
1936028450
]
,
[
0
,
268435456
,
1835316017
,
2041
]
]
]
,
dtype
=
int32
)
In
[
66
]
:
np
.
eye
(
5
)
Out
[
66
]
:
array
(
[
[
1.
,
0.
,
0.
,
0.
,
0.
]
,
[
0.
,
1.
,
0.
,
0.
,
0.
]
,
[
0.
,
0.
,
1.
,
0.
,
0.
]
,
[
0.
,
0.
,
0.
,
1.
,
0.
]
,
[
0.
,
0.
,
0.
,
0.
,
1.
]
]
)
In
[
67
]
:
g
=
np
.
linspace
(
5
,
15
,
12
)
g
Out
[
67
]
:
array
(
[
5.
,
5.90909091
,
6.81818182
,
7.72727273
,
8.63636364
,
9.54545455
,
10.45454545
,
11.36363636
,
12.27272727
,
13.18181818
,
14.09090909
,
15.
]
)
Crea un objeto
ndarray
prepoblado con ceros.Crea un objeto
ndarray
prepoblado con unos.Lo mismo, pero toma otro objeto
ndarray
para deducir la forma.Crea un objeto
ndarray
no prepoblado con nada (los números dependen de los bits presentes en la memoria).Crea una matriz cuadrada como un objeto
ndarray
con la diagonal poblada por unos.Crea un objeto unidimensional
ndarray
con intervalos uniformemente espaciados entre los números; los parámetros utilizados sonstart
,end
, ynum
(número de elementos).
Para todas estas funciones, puedes proporcionar los siguientes parámetros:
shape
-
Puede ser un
int
, una secuencia de objetosint
o una referencia a otrondarray
dtype
(opcional)-
A
dtype
-estos son tipos de datosNumPy
-específicos para objetosndarray
order
(opcional)-
El orden de almacenamiento de los elementos en la memoria:
C
para C (es decir, por filas) oF
para Fortran (es decir, por columnas)
Aquí se hace evidente cómo NumPy
especializa la construcción de matrices con la clase ndarray
, en comparación con el enfoque basado en list
:
-
El objeto
ndarray
tiene dimensiones incorporadas (ejes). -
El objeto
ndarray
es inmutable; su longitud (tamaño) es fija. -
Sólo permite un único tipo de datos (
np.dtype
) para toda la matriz.
En cambio, la clase array
sólo comparte la característica de permitir un único tipo de datos (código de tipo, dtype
).
La función del parámetro order
se trata más adelante en el capítulo. La Tabla 4-1 ofrece una visión general de los objetos np.dtype
seleccionados (es decir, los tipos de datos básicos que permite NumPy
).
dtype | Descripción | Ejemplo |
---|---|---|
|
Booleano |
|
|
Entero con signo |
|
|
Entero sin signo |
|
|
Punto flotante |
|
|
Punto flotante complejo |
|
|
|
|
|
|
|
|
Objeto |
|
|
Unicode |
|
|
Datos brutos (vacío) |
|
Remodelar y redimensionar
Aunque los objetos ndarray
son inmutables por defecto, existen múltiples opciones para remodelar y redimensionar un objeto de este tipo. Mientras que la remodelación, en general, sólo proporciona otra visión de los mismos datos, el redimensionamiento, en general, crea un nuevo objeto (temporal). Primero, algunos ejemplos de remodelación:
In
[
74
]
:
g
=
np
.
arange
(
15
)
In
[
75
]
:
g
Out
[
75
]
:
array
(
[
0
,
1
,
2
,
3
,
4
,
5
,
6
,
7
,
8
,
9
,
10
,
11
,
12
,
13
,
14
]
)
In
[
76
]
:
g
.
shape
Out
[
76
]
:
(
15
,
)
In
[
77
]
:
np
.
shape
(
g
)
Out
[
77
]
:
(
15
,
)
In
[
78
]
:
g
.
reshape
(
(
3
,
5
)
)
Out
[
78
]
:
array
(
[
[
0
,
1
,
2
,
3
,
4
]
,
[
5
,
6
,
7
,
8
,
9
]
,
[
10
,
11
,
12
,
13
,
14
]
]
)
In
[
79
]
:
h
=
g
.
reshape
(
(
5
,
3
)
)
h
Out
[
79
]
:
array
(
[
[
0
,
1
,
2
]
,
[
3
,
4
,
5
]
,
[
6
,
7
,
8
]
,
[
9
,
10
,
11
]
,
[
12
,
13
,
14
]
]
)
In
[
80
]
:
h
.
T
Out
[
80
]
:
array
(
[
[
0
,
3
,
6
,
9
,
12
]
,
[
1
,
4
,
7
,
10
,
13
]
,
[
2
,
5
,
8
,
11
,
14
]
]
)
In
[
81
]
:
h
.
transpose
(
)
Out
[
81
]
:
array
(
[
[
0
,
3
,
6
,
9
,
12
]
,
[
1
,
4
,
7
,
10
,
13
]
,
[
2
,
5
,
8
,
11
,
14
]
]
)
La forma del objeto original
ndarray
.Remodelación a dos dimensiones (vista de memoria).
Crear un nuevo objeto.
La transposición del nuevo objeto
ndarray
.
Durante una operación de redimensionamiento, el número total de elementos del objeto ndarray
no cambia. Durante una operación de redimensionamiento, este número cambia: disminuye ("down-sizing") o aumenta ("up-sizing"). Aquí tienes algunos ejemplos de redimensionamiento:
In
[
82
]
:
g
Out
[
82
]
:
array
(
[
0
,
1
,
2
,
3
,
4
,
5
,
6
,
7
,
8
,
9
,
10
,
11
,
12
,
13
,
14
]
)
In
[
83
]
:
np
.
resize
(
g
,
(
3
,
1
)
)
Out
[
83
]
:
array
(
[
[
0
]
,
[
1
]
,
[
2
]
]
)
In
[
84
]
:
np
.
resize
(
g
,
(
1
,
5
)
)
Out
[
84
]
:
array
(
[
[
0
,
1
,
2
,
3
,
4
]
]
)
In
[
85
]
:
np
.
resize
(
g
,
(
2
,
5
)
)
Out
[
85
]
:
array
(
[
[
0
,
1
,
2
,
3
,
4
]
,
[
5
,
6
,
7
,
8
,
9
]
]
)
In
[
86
]
:
n
=
np
.
resize
(
g
,
(
5
,
4
)
)
n
Out
[
86
]
:
array
(
[
[
0
,
1
,
2
,
3
]
,
[
4
,
5
,
6
,
7
]
,
[
8
,
9
,
10
,
11
]
,
[
12
,
13
,
14
,
0
]
,
[
1
,
2
,
3
,
4
]
]
)
El apilamiento es una operación especial de que permite combinar horizontal o verticalmente dos objetos de ndarray
. Sin embargo, el tamaño de la dimensión de "conexión" debe ser el mismo:
In
[
87
]
:
h
Out
[
87
]
:
array
(
[
[
0
,
1
,
2
]
,
[
3
,
4
,
5
]
,
[
6
,
7
,
8
]
,
[
9
,
10
,
11
]
,
[
12
,
13
,
14
]
]
)
In
[
88
]
:
np
.
hstack
(
(
h
,
2
*
h
)
)
Out
[
88
]
:
array
(
[
[
0
,
1
,
2
,
0
,
2
,
4
]
,
[
3
,
4
,
5
,
6
,
8
,
10
]
,
[
6
,
7
,
8
,
12
,
14
,
16
]
,
[
9
,
10
,
11
,
18
,
20
,
22
]
,
[
12
,
13
,
14
,
24
,
26
,
28
]
]
)
In
[
89
]
:
np
.
vstack
(
(
h
,
0.5
*
h
)
)
Out
[
89
]
:
array
(
[
[
0.
,
1.
,
2.
]
,
[
3.
,
4.
,
5.
]
,
[
6.
,
7.
,
8.
]
,
[
9.
,
10.
,
11.
]
,
[
12.
,
13.
,
14.
]
,
[
0.
,
0.5
,
1.
]
,
[
1.5
,
2.
,
2.5
]
,
[
3.
,
3.5
,
4.
]
,
[
4.5
,
5.
,
5.5
]
,
[
6.
,
6.5
,
7.
]
]
)
Otra operación especial es el aplanamiento de un objeto multidimensional ndarray
a uno unidimensional. Puedes elegir si el aplanamiento se realiza fila a fila (ordenC
) o columna a columna (ordenF
):
In
[
90
]
:
h
Out
[
90
]
:
array
(
[
[
0
,
1
,
2
]
,
[
3
,
4
,
5
]
,
[
6
,
7
,
8
]
,
[
9
,
10
,
11
]
,
[
12
,
13
,
14
]
]
)
In
[
91
]
:
h
.
flatten
(
)
Out
[
91
]
:
array
(
[
0
,
1
,
2
,
3
,
4
,
5
,
6
,
7
,
8
,
9
,
10
,
11
,
12
,
13
,
14
]
)
In
[
92
]
:
h
.
flatten
(
order
=
'
C
'
)
Out
[
92
]
:
array
(
[
0
,
1
,
2
,
3
,
4
,
5
,
6
,
7
,
8
,
9
,
10
,
11
,
12
,
13
,
14
]
)
In
[
93
]
:
h
.
flatten
(
order
=
'
F
'
)
Out
[
93
]
:
array
(
[
0
,
3
,
6
,
9
,
12
,
1
,
4
,
7
,
10
,
13
,
2
,
5
,
8
,
11
,
14
]
)
In
[
94
]
:
for
i
in
h
.
flat
:
(
i
,
end
=
'
,
'
)
0
,
1
,
2
,
3
,
4
,
5
,
6
,
7
,
8
,
9
,
10
,
11
,
12
,
13
,
14
,
In
[
95
]
:
for
i
in
h
.
ravel
(
order
=
'
C
'
)
:
(
i
,
end
=
'
,
'
)
0
,
1
,
2
,
3
,
4
,
5
,
6
,
7
,
8
,
9
,
10
,
11
,
12
,
13
,
14
,
In
[
96
]
:
for
i
in
h
.
ravel
(
order
=
'
F
'
)
:
(
i
,
end
=
'
,
'
)
0
,
3
,
6
,
9
,
12
,
1
,
4
,
7
,
10
,
13
,
2
,
5
,
8
,
11
,
14
,
Matrices booleanas
La comparación y las operaciones lógicas en general funcionan sobre los objetos ndarray
del mismo modo, por elementos, que sobre los tipos de datos estándar de Python. Las condiciones de evaluación producen por defecto un objeto booleano ndarray
(dtype
es bool
):
In
[
97
]
:
h
Out
[
97
]
:
array
(
[
[
0
,
1
,
2
]
,
[
3
,
4
,
5
]
,
[
6
,
7
,
8
]
,
[
9
,
10
,
11
]
,
[
12
,
13
,
14
]
]
)
In
[
98
]
:
h
>
8
Out
[
98
]
:
array
(
[
[
False
,
False
,
False
]
,
[
False
,
False
,
False
]
,
[
False
,
False
,
False
]
,
[
True
,
True
,
True
]
,
[
True
,
True
,
True
]
]
)
In
[
99
]
:
h
<
=
7
Out
[
99
]
:
array
(
[
[
True
,
True
,
True
]
,
[
True
,
True
,
True
]
,
[
True
,
True
,
False
]
,
[
False
,
False
,
False
]
,
[
False
,
False
,
False
]
]
)
In
[
100
]
:
h
==
5
Out
[
100
]
:
array
(
[
[
False
,
False
,
False
]
,
[
False
,
False
,
True
]
,
[
False
,
False
,
False
]
,
[
False
,
False
,
False
]
,
[
False
,
False
,
False
]
]
)
In
[
101
]
:
(
h
==
5
)
.
astype
(
int
)
Out
[
101
]
:
array
(
[
[
0
,
0
,
0
]
,
[
0
,
0
,
1
]
,
[
0
,
0
,
0
]
,
[
0
,
0
,
0
]
,
[
0
,
0
,
0
]
]
)
In
[
102
]
:
(
h
>
4
)
&
(
h
<
=
12
)
Out
[
102
]
:
array
(
[
[
False
,
False
,
False
]
,
[
False
,
False
,
True
]
,
[
True
,
True
,
True
]
,
[
True
,
True
,
True
]
,
[
True
,
False
,
False
]
]
)
¿El valor es mayor que ...?
¿El valor es menor o igual que ...?
¿El valor es igual a ...?
Presenta
True
yFalse
como valores enteros 0 y 1.¿El valor es mayor que ... y menor o igual que ...?
Estas matrices booleanas pueden utilizarse para indexar y seleccionar datos. Observa que las siguientes operaciones aplanan los datos:
In
[
103
]
:
h
[
h
>
8
]
Out
[
103
]
:
array
(
[
9
,
10
,
11
,
12
,
13
,
14
]
)
In
[
104
]
:
h
[
(
h
>
4
)
&
(
h
<
=
12
)
]
Out
[
104
]
:
array
(
[
5
,
6
,
7
,
8
,
9
,
10
,
11
,
12
]
)
In
[
105
]
:
h
[
(
h
<
4
)
|
(
h
>
=
12
)
]
Out
[
105
]
:
array
(
[
0
,
1
,
2
,
3
,
12
,
13
,
14
]
)
Dame todos los valores mayores que ...
Dame todos los valores mayores que ... y menores o iguales que ...
Dame todos los valores mayores que ... o menores o iguales que ...
Una potente herramienta en este sentido es la función np.where()
, que permite definir acciones/operaciones en función de si una condición es True
o False
. El resultado de aplicar np.where()
es un nuevo objeto ndarray
con la misma forma que el original:
In
[
106
]
:
np
.
where
(
h
>
7
,
1
,
0
)
Out
[
106
]
:
array
(
[
[
0
,
0
,
0
]
,
[
0
,
0
,
0
]
,
[
0
,
0
,
1
]
,
[
1
,
1
,
1
]
,
[
1
,
1
,
1
]
]
)
In
[
107
]
:
np
.
where
(
h
%
2
==
0
,
'
even
'
,
'
odd
'
)
Out
[
107
]
:
array
(
[
[
'
even
'
,
'
odd
'
,
'
even
'
]
,
[
'
odd
'
,
'
even
'
,
'
odd
'
]
,
[
'
even
'
,
'
odd
'
,
'
even
'
]
,
[
'
odd
'
,
'
even
'
,
'
odd
'
]
,
[
'
even
'
,
'
odd
'
,
'
even
'
]
]
,
dtype
=
'
<U4
'
)
In
[
108
]
:
np
.
where
(
h
<
=
7
,
h
*
2
,
h
/
2
)
Out
[
108
]
:
array
(
[
[
0.
,
2.
,
4.
]
,
[
6.
,
8.
,
10.
]
,
[
12.
,
14.
,
4.
]
,
[
4.5
,
5.
,
5.5
]
,
[
6.
,
6.5
,
7.
]
]
)
En el nuevo objeto, establece
1
si esTrue
y0
en caso contrario.En el nuevo objeto, establece
even
si esTrue
yodd
en caso contrario.En el nuevo objeto, pon dos veces el elemento
h
si esTrue
y la mitad del elementoh
en caso contrario.
En capítulos posteriores encontrarás más ejemplos de estas importantes operaciones con objetos de ndarray
.
Comparación de velocidades
En breve pasaremos a las matrices estructuradas con NumPy
, pero sigamos con las matrices normales por un momento y veamos qué aporta la especialización en términos de rendimiento.
Como ejemplo sencillo, considera la generación de una matriz/matriz de forma 5.000 × 5.000 elementos, poblada con números pseudoaleatorios estándar distribuidos normalmente. A continuación, se calculará la suma de todos los elementos. En primer lugar, el enfoque Python puro, en el que se utilizan las comprensiones list
:
In
[
109
]
:
import
random
I
=
5000
In
[
110
]
:
%
time
mat
=
[
[
random
.
gauss
(
0
,
1
)
for
j
in
range
(
I
)
]
\
for
i
in
range
(
I
)
]
CPU
times
:
user
17.1
s
,
sys
:
361
ms
,
total
:
17.4
s
Wall
time
:
17.4
s
In
[
111
]
:
mat
[
0
]
[
:
5
]
Out
[
111
]
:
[
-
0.40594967782329183
,
-
1.357757478015285
,
0.05129566894355976
,
-
0.8958429976582192
,
0.6234174778878331
]
In
[
112
]
:
%
time
sum
(
[
sum
(
l
)
for
l
in
mat
]
)
CPU
times
:
user
142
ms
,
sys
:
1.69
ms
,
total
:
144
ms
Wall
time
:
143
ms
Out
[
112
]
:
-
3561.944965714259
In
[
113
]
:
import
sys
sum
(
[
sys
.
getsizeof
(
l
)
for
l
in
mat
]
)
Out
[
113
]
:
215200000
La creación de la matriz mediante una comprensión anidada de
list
.Algunos seleccionaron números al azar de entre los sorteados.
Las sumas de los objetos individuales
list
se calculan primero durante la comprensión de una lista; luego se toma la suma de las sumas.Esto suma el uso de memoria de todos los objetos de
list
.
Pasemos ahora a NumPy
y veamos cómo se resuelve allí el mismo problema. Por comodidad, el subpaquete NumPy
random
ofrece multitud de funciones para instanciar un objeto ndarray
y poblarlo al mismo tiempo con números pseudoaleatorios:
In
[
114
]
:
%
time
mat
=
np
.
random
.
standard_normal
(
(
I
,
I
)
)
CPU
times
:
user
1.01
s
,
sys
:
200
ms
,
total
:
1.21
s
Wall
time
:
1.21
s
In
[
115
]
:
%
time
mat
.
sum
(
)
CPU
times
:
user
29.7
ms
,
sys
:
1.15
ms
,
total
:
30.8
ms
Wall
time
:
29.4
ms
Out
[
115
]
:
-
186.12767026606448
In
[
116
]
:
mat
.
nbytes
Out
[
116
]
:
200000000
In
[
117
]
:
sys
.
getsizeof
(
mat
)
Out
[
117
]
:
200000112
Crea el objeto
ndarray
con números aleatorios estándar distribuidos normalmente; es más rápido por un factor de aproximadamente 14.Calcula la suma de todos los valores del objeto
ndarray
; es más rápido por un factor de 4,5.El enfoque
NumPy
también ahorra algo de memoria, ya que la sobrecarga de memoria del objetondarray
es minúscula en comparación con el tamaño de los propios datos.
Utilizar matrices NumPy
El uso de NumPy
para operaciones y algoritmos basados en matrices suele dar como resultado un código compacto y fácil de leer, y mejoras significativas de rendimiento respecto al código Python puro.
Matrices NumPy estructuradas
La especialización de la clase ndarray
conlleva, obviamente, una serie de valiosas ventajas. Sin embargo, una especialización demasiado estrecha podría resultar una carga demasiado pesada para la mayoría de los algoritmos y aplicaciones basados en arrays. Por ello, NumPy
proporciona objetosestructurados ndarray
y de registro recarray
que te permiten tener un dtype
diferente por columna. ¿Qué significa "por columna"? Considera la siguiente inicialización de un objeto estructurado ndarray
:
In
[
118
]
:
dt
=
np
.
dtype
(
[
(
'
Name
'
,
'
S10
'
)
,
(
'
Age
'
,
'
i4
'
)
,
(
'
Height
'
,
'
f
'
)
,
(
'
Children/Pets
'
,
'
i4
'
,
2
)
]
)
In
[
119
]
:
dt
Out
[
119
]
:
dtype
(
[
(
'
Name
'
,
'
S10
'
)
,
(
'
Age
'
,
'
<i4
'
)
,
(
'
Height
'
,
'
<f4
'
)
,
(
'
Children/Pets
'
,
'
<i4
'
,
(
2
,
)
)
]
)
In
[
120
]
:
dt
=
np
.
dtype
(
{
'
names
'
:
[
'
Name
'
,
'
Age
'
,
'
Height
'
,
'
Children/Pets
'
]
,
'
formats
'
:
'
O int float int,int
'
.
split
(
)
}
)
In
[
121
]
:
dt
Out
[
121
]
:
dtype
(
[
(
'
Name
'
,
'
O
'
)
,
(
'
Age
'
,
'
<i8
'
)
,
(
'
Height
'
,
'
<f8
'
)
,
(
'
Children/Pets
'
,
[
(
'
f0
'
,
'
<i8
'
)
,
(
'
f1
'
,
'
<i8
'
)
]
)
]
)
In
[
122
]
:
s
=
np
.
array
(
[
(
'
Smith
'
,
45
,
1.83
,
(
0
,
1
)
)
,
(
'
Jones
'
,
53
,
1.72
,
(
2
,
2
)
)
]
,
dtype
=
dt
)
In
[
123
]
:
s
Out
[
123
]
:
array
(
[
(
'
Smith
'
,
45
,
1.83
,
(
0
,
1
)
)
,
(
'
Jones
'
,
53
,
1.72
,
(
2
,
2
)
)
]
,
dtype
=
[
(
'
Name
'
,
'
O
'
)
,
(
'
Age
'
,
'
<i8
'
)
,
(
'
Height
'
,
'
<f8
'
)
,
(
'
Children/Pets
'
,
[
(
'
f0
'
,
'
<i8
'
)
,
(
'
f1
'
,
'
<i8
'
)
]
)
]
)
In
[
124
]
:
type
(
s
)
Out
[
124
]
:
numpy
.
ndarray
El complejo
dtype
está compuesto.Una sintaxis alternativa para conseguir el mismo resultado.
El
ndarray
estructurado se instanciará con dos registros.El tipo de objeto sigue siendo
ndarray
.
En cierto sentido, esta construcción se parece bastante a la operación de inicialización de tablas en una base de datos SQL: se tienen los nombres de las columnas y los tipos de datos de las columnas, quizá con alguna información adicional (por ejemplo, el número máximo de caracteres por objeto str
). Ahora se puede acceder fácilmente a las columnas individuales por sus nombres y a las filas por sus valores de índice:
In
[
125
]
:
s
[
'
Name
'
]
Out
[
125
]
:
array
(
[
'
Smith
'
,
'
Jones
'
]
,
dtype
=
object
)
In
[
126
]
:
s
[
'
Height
'
]
.
mean
(
)
Out
[
126
]
:
1.775
In
[
127
]
:
s
[
0
]
Out
[
127
]
:
(
'
Smith
'
,
45
,
1.83
,
(
0
,
1
)
)
In
[
128
]
:
s
[
1
]
[
'
Age
'
]
Out
[
128
]
:
53
Seleccionar una columna por su nombre.
Llamar a un método en una columna seleccionada.
Seleccionar un registro.
Seleccionar un campo en un registro.
En resumen, las matrices estructuradas son una generalización del tipo de objeto normal ndarray
, en el sentido de que el tipo de datos sólo tiene que ser el mismo por columna, como en las tablas de las bases de datos SQL. Una ventaja de las matrices estructuradas es que un solo elemento de una columna puede ser otro objeto multidimensional y no tiene por qué ajustarse a los tipos de datos básicos de NumPy
.
Matrices estructuradas
NumPy
proporciona, además de matrices normales, matrices estructuradas (y de registros) que permiten describir y manejar estructuras de datos similares a tablas con una variedad de tipos de datos diferentes por columna (con nombre). Traen a Python estructuras de datos similares a tablas SQL, con la mayoría de las ventajas de los objetos normales ndarray
(sintaxis, métodos, rendimiento).
Vectorización del código
La vectorización es una estrategia de para conseguir un código más compacto que posiblemente se ejecute más rápido. La idea fundamental es realizar una operación o aplicar una función a un objeto complejo "de una vez" y no haciendo un bucle sobre los elementos individuales del objeto. En Python, las herramientas de programación funcional como map()
y filter()
proporcionan algunos medios básicos para la vectorización. Sin embargo, NumPy
tiene la vectorización incorporada en lo más profundo de su núcleo.
Vectorización básica
Como se ha demostrado en el apartado anterior, las operaciones matemáticas sencillas -como calcular la suma de todos los elementos- pueden implementarse en los objetos ndarray
directamente (mediante métodos o funciones universales). También son posibles operaciones vectoriales más generales. Por ejemplo, se pueden sumar dos matrices NumPy
elemento a elemento de la siguiente manera:
In
[
129
]
:
np
.
random
.
seed
(
100
)
r
=
np
.
arange
(
12
)
.
reshape
(
(
4
,
3
)
)
s
=
np
.
arange
(
12
)
.
reshape
(
(
4
,
3
)
)
*
0.5
In
[
130
]
:
r
Out
[
130
]
:
array
(
[
[
0
,
1
,
2
]
,
[
3
,
4
,
5
]
,
[
6
,
7
,
8
]
,
[
9
,
10
,
11
]
]
)
In
[
131
]
:
s
Out
[
131
]
:
array
(
[
[
0.
,
0.5
,
1.
]
,
[
1.5
,
2.
,
2.5
]
,
[
3.
,
3.5
,
4.
]
,
[
4.5
,
5.
,
5.5
]
]
)
In
[
132
]
:
r
+
s
Out
[
132
]
:
array
(
[
[
0.
,
1.5
,
3.
]
,
[
4.5
,
6.
,
7.5
]
,
[
9.
,
10.5
,
12.
]
,
[
13.5
,
15.
,
16.5
]
]
)
El primer objeto
ndarray
con números aleatorios.El segundo objeto
ndarray
con números aleatorios.Suma de elementos como operación vectorizada (sin bucles).
NumPy
también admite lo que se denomina difusión. Esto te permite combinar objetos de distinta forma en una sola operación. Los ejemplos anteriores ya han hecho uso de esto. Considera los siguientes ejemplos:
In
[
133
]
:
r
+
3
Out
[
133
]
:
array
(
[
[
3
,
4
,
5
]
,
[
6
,
7
,
8
]
,
[
9
,
10
,
11
]
,
[
12
,
13
,
14
]
]
)
In
[
134
]
:
2
*
r
Out
[
134
]
:
array
(
[
[
0
,
2
,
4
]
,
[
6
,
8
,
10
]
,
[
12
,
14
,
16
]
,
[
18
,
20
,
22
]
]
)
In
[
135
]
:
2
*
r
+
3
Out
[
135
]
:
array
(
[
[
3
,
5
,
7
]
,
[
9
,
11
,
13
]
,
[
15
,
17
,
19
]
,
[
21
,
23
,
25
]
]
)
Durante la adición escalar, el escalar se difunde y se añade a cada elemento.
Durante la multiplicación escalar, el escalar también se transmite a cada elemento y se multiplica con él.
Esta transformación lineal combina ambas operaciones.
Estas operaciones también funcionan con objetos ndarray
de formas diferentes, hasta cierto punto:
In
[
136
]
:
r
Out
[
136
]
:
array
(
[
[
0
,
1
,
2
]
,
[
3
,
4
,
5
]
,
[
6
,
7
,
8
]
,
[
9
,
10
,
11
]
]
)
In
[
137
]
:
r
.
shape
Out
[
137
]
:
(
4
,
3
)
In
[
138
]
:
s
=
np
.
arange
(
0
,
12
,
4
)
s
Out
[
138
]
:
array
(
[
0
,
4
,
8
]
)
In
[
139
]
:
r
+
s
Out
[
139
]
:
array
(
[
[
0
,
5
,
10
]
,
[
3
,
8
,
13
]
,
[
6
,
11
,
16
]
,
[
9
,
14
,
19
]
]
)
In
[
140
]
:
s
=
np
.
arange
(
0
,
12
,
3
)
s
Out
[
140
]
:
array
(
[
0
,
3
,
6
,
9
]
)
In
[
141
]
:
r
+
s
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
ValueErrorTraceback
(
most
recent
call
last
)
<
ipython
-
input
-
141
-
1890
b26ec965
>
in
<
module
>
(
)
-
-
-
-
>
1
r
+
s
ValueError
:
operands
could
not
be
broadcast
together
with
shapes
(
4
,
3
)
(
4
,
)
In
[
142
]
:
r
.
transpose
(
)
+
s
Out
[
142
]
:
array
(
[
[
0
,
6
,
12
,
18
]
,
[
1
,
7
,
13
,
19
]
,
[
2
,
8
,
14
,
20
]
]
)
In
[
143
]
:
sr
=
s
.
reshape
(
-
1
,
1
)
sr
Out
[
143
]
:
array
(
[
[
0
]
,
[
3
]
,
[
6
]
,
[
9
]
]
)
In
[
144
]
:
sr
.
shape
Out
[
144
]
:
(
4
,
1
)
In
[
145
]
:
r
+
s
.
reshape
(
-
1
,
1
)
Out
[
145
]
:
array
(
[
[
0
,
1
,
2
]
,
[
6
,
7
,
8
]
,
[
12
,
13
,
14
]
,
[
18
,
19
,
20
]
]
)
Un nuevo objeto unidimensional
ndarray
de longitud 3.Los objetos
r
(matriz) ys
(vector) pueden añadirse directamente.Otro objeto unidimensional
ndarray
de longitud 4.La longitud del nuevo objeto
s
(vector) es ahora diferente de la longitud de la segunda dimensión del objetor
.Transponer de nuevo el objeto
r
permite la suma vectorizada.Alternativamente, se puede cambiar la forma de
s
a(4, 1)
para que funcione la suma (sin embargo, los resultados son diferentes).
A menudo, las funciones Python definidas a medida también funcionan con objetos ndarray
. Si la implementación lo permite, las matrices pueden utilizarse con funciones del mismo modo que los objetos int
o float
. Considera la siguiente función:
In
[
146
]
:
def
f
(
x
)
:
return
3
*
x
+
5
In
[
147
]
:
f
(
0.5
)
Out
[
147
]
:
6.5
In
[
148
]
:
f
(
r
)
Out
[
148
]
:
array
(
[
[
5
,
8
,
11
]
,
[
14
,
17
,
20
]
,
[
23
,
26
,
29
]
,
[
32
,
35
,
38
]
]
)
Una sencilla función de Python que implementa una transformación lineal en el parámetro
x
.La función
f()
aplicada a un objeto Pythonfloat
.La misma función aplicada a un objeto
ndarray
, dando como resultado una evaluación vectorizada y por elementos de la función.
Lo que hace NumPy
es simplemente aplicar la función f
al objeto elemento a elemento. En ese sentido, al utilizar este tipo de operación no se evitan los bucles; sólo se evitan en el nivel Python y se delega el bucle en NumPy
. En el nivel NumPy
, de los bucles sobre el objeto ndarray
se encarga el código optimizado, la mayor parte de él escrito en C y, por tanto, generalmente más rápido que el Python puro. Esto explica el "secret
" que hay detrás de las ventajas de rendimiento que supone utilizar NumPy
para casos de uso basados en matrices.
Disposición de la memoria
Cuando los objetos ndarray
se inicializan utilizando np.zeros()
, como en "Dimensiones múltiples", se proporciona un argumento opcional para la disposición en memoria. Este argumento especifica, a grandes rasgos, qué elementos de una matriz se almacenan en memoria unos junto a otros (contiguamente). Cuando se trabaja con matrices pequeñas, esto apenas tiene un impacto apreciable en el rendimiento de las operaciones con matrices. Sin embargo, cuando las matrices se hacen grandes, y dependiendo del algoritmo (financiero) que se implemente en ellas, la historia puede ser diferente. Es entonces cuando entra en juego la disposición de la memoria (consulta, por ejemplo, el artículo de Eli Bendersky "Memory Layout of Multi-Dimensional Arrays").
Para ilustrar la importancia potencial de la disposición en memoria de las matrices en ciencia y finanzas, considera la siguiente construcción de objetos multidimensionales ndarray
:
In
[
149
]
:
x
=
np
.
random
.
standard_normal
(
(
1000000
,
5
)
)
In
[
150
]
:
y
=
2
*
x
+
3
In
[
151
]
:
C
=
np
.
array
(
(
x
,
y
)
,
order
=
'
C
'
)
In
[
152
]
:
F
=
np
.
array
(
(
x
,
y
)
,
order
=
'
F
'
)
In
[
153
]
:
x
=
0.0
;
y
=
0.0
In
[
154
]
:
C
[
:
2
]
.
round
(
2
)
Out
[
154
]
:
array
(
[
[
[
-
1.75
,
0.34
,
1.15
,
-
0.25
,
0.98
]
,
[
0.51
,
0.22
,
-
1.07
,
-
0.19
,
0.26
]
,
[
-
0.46
,
0.44
,
-
0.58
,
0.82
,
0.67
]
,
.
.
.
,
[
-
0.05
,
0.14
,
0.17
,
0.33
,
1.39
]
,
[
1.02
,
0.3
,
-
1.23
,
-
0.68
,
-
0.87
]
,
[
0.83
,
-
0.73
,
1.03
,
0.34
,
-
0.46
]
]
,
[
[
-
0.5
,
3.69
,
5.31
,
2.5
,
4.96
]
,
[
4.03
,
3.44
,
0.86
,
2.62
,
3.51
]
,
[
2.08
,
3.87
,
1.83
,
4.63
,
4.35
]
,
.
.
.
,
[
2.9
,
3.28
,
3.33
,
3.67
,
5.78
]
,
[
5.04
,
3.6
,
0.54
,
1.65
,
1.26
]
,
[
4.67
,
1.54
,
5.06
,
3.69
,
2.07
]
]
]
)
Un objeto
ndarray
con gran asimetría en las dos dimensiones.Una transformación lineal de los datos del objeto original.
Esto crea un objeto bidimensional
ndarray
con el ordenC
(fila-mayor).Esto crea un objeto bidimensional
ndarray
con el ordenF
(columna-mayor).Se libera memoria (depende de la recogida de basura).
Algunos números del objeto
C
.
Veamos algunos ejemplos fundamentales y casos de uso de ambos tipos de objetos ndarray
y consideremos la velocidad con la que se ejecutan, dadas las diferentes disposiciones de memoria:
In
[
155
]
:
%
timeit
C
.
sum
(
)
4.36
ms
±
89.3
µ
s
per
loop
(
mean
±
std
.
dev
.
of
7
runs
,
100
loops
each
)
In
[
156
]
:
%
timeit
F
.
sum
(
)
4.21
ms
±
71.4
µ
s
per
loop
(
mean
±
std
.
dev
.
of
7
runs
,
100
loops
each
)
In
[
157
]
:
%
timeit
C
.
sum
(
axis
=
0
)
17.9
ms
±
776
µ
s
per
loop
(
mean
±
std
.
dev
.
of
7
runs
,
100
loops
each
)
In
[
158
]
:
%
timeit
C
.
sum
(
axis
=
1
)
35.1
ms
±
999
µ
s
per
loop
(
mean
±
std
.
dev
.
of
7
runs
,
10
loops
each
)
In
[
159
]
:
%
timeit
F
.
sum
(
axis
=
0
)
83.8
ms
±
2.63
ms
per
loop
(
mean
±
std
.
dev
.
of
7
runs
,
10
loops
each
)
In
[
160
]
:
%
timeit
F
.
sum
(
axis
=
1
)
67.9
ms
±
5.16
ms
per
loop
(
mean
±
std
.
dev
.
of
7
runs
,
10
loops
each
)
In
[
161
]
:
F
=
0.0
;
C
=
0.0
Calcula la suma de todos los elementos.
Calcula las sumas por fila ("muchos").
Calcula las sumas por columnas ("pocas").
Podemos resumir los resultados de rendimiento como sigue:
-
Al calcular la suma de todos los elementos, la disposición de la memoria no importa realmente.
-
La suma sobre los objetos
C
-ordenadosndarray
es más rápida tanto sobre filas como sobre columnas (una ventaja de velocidad absoluta ). -
C
Con el objetondarray
ordenado (fila-mayor), la suma de filas es relativamente más rápida que la suma de columnas. -
F
Con el objetondarray
ordenado por columnas, la suma de columnas es relativamente más rápida que la suma de filas.
Conclusión
NumPy
es el paquete de elección para la computación numérica en Python. La clase ndarray
está diseñada específicamente para ser cómoda y eficaz en el manejo de datos numéricos (grandes). Sus potentes métodos y las funciones universales NumPy
permiten un código vectorizado que, en su mayor parte, evita los bucles lentos a nivel de Python. Muchos de los enfoques introducidos en este capítulo se trasladan también a pandas
y a su clase DataFrame
(véase el Capítulo 5).
Otros recursos
En el sitio web NumPy
encontrarás muchos recursos útiles:
Buenas introducciones a NumPy
en forma de libro son:
-
McKinney, Wes (2017). Python para el Análisis de Datos. Sebastopol, CA: O'Reilly.
-
VanderPlas, Jake (2016). Manual de Ciencia de Datos en Python. Sebastopol, CA: O'Reilly.
Get Python para Finanzas, 2ª Edición now with the O’Reilly learning platform.
O’Reilly members experience books, live events, courses curated by job role, and more from O’Reilly and nearly 200 top publishers.