jueves, 31 de octubre de 2019

Tipos de datos en arrays Numpy (dtype)




Formas de definir el tipo de datos en un array


Básicamente, los datos (elementos) que puede contener un array Numpy son cadenas de caracteres, números enteros de distintos tamaños, números reales y booleanos. (A partir de Numpy 1.7 es posible declarar arrays para fechas y horas con el tipo de dato datetime64 pero de momento esta opción se considera experimental y puede sufrir cambios).

La propiedad dtype se utiliza para definir y acceder al tipo de datos de un array:

a = np.array([0, 0, 1, 1, 0])
a.dtype  # dtype('int64')  

En este ejemplo dtype ha devuelto que los datos del array son enteros de 64 bits (8 bytes).

Existen distintos modos de definir el tipo de datos de un array Numpy:


1. Implícita


Numpy analiza los datos y establece el tipo, automáticamente.

a = np.array([0, 0, 1, 1, 0])  # Entero (int)
print(a)  # [0 0 1 1 0]
a.dtype  # dtype('int64')

a = np.array([0.1, 0.4, 1.0, 1.5, 0.9])  # Flotante (float)
print(a)  # [0.1 0.4 1.  1.5 0.9]
a.dtype  # dtype('float64')

a = np.array(['0', '0', '1', '1', '0'])  # Cadena Unicode (str)
print(a)  # ['0' '0' '1' '1' '0']
a.dtype  # dtype('<U1')

a = np.array([False, False, True, True, False])  # Booleano (bool)
print(a)  # [False False  True  True False]
a.dtype  # dtype('bool')


2. Con los tipos genéricos


int, float, str, bool

Se establece asignando un tipo genérico Python al argumento dtype.

a = np.array([0, 0, 1, 1, 0], dtype=int)  # Entero (int)
print(a)  # [0 0 1 1 0]
a.dtype  # dtype('int64')

a = np.array([0.1, 0.4, 0.9], dtype=float)  # Flotante (float)
print(a)  # [0.1 0.4 0.9]
a.dtype  # dtype('float64')

a = np.array(['0', '0', '1', '1', '0'], dtype=str)  # Unicode (str)
print(a)  # ['0' '0' '1' '1' '0']
a.dtype  # dtype('<U1')

a = np.array([0, 0, 1, 1, 0], dtype=bool)  # Booleano (bool)
print(a)  # [False False  True  True False]
a.dtype  # dtype('bool')


3. Con los tipos de Numpy


np.int, np.float, np.str, np.bool, etc.

Se establece asignando uno de los tipos de Numpy al argumento dtype.

a = np.array([0, 0, 1, 1, 0], dtype=np.int)
print(a)  # [0 0 1 1 0]
a.dtype  # dtype('int64')

Para optimizar el uso de los recursos del sistema es importante definir el tipo de dato apropiado de los arrays en función a los datos y al tamaño que se prevean van a contener y procesar.

Los modos implícitos o de tipos genéricos con números enteros pequeños reservan demasiado espacio de memoria para cada dato: 8 bytes. Esto no es ningún problema cuando son pocos los datos a tratar pero con arrays de gran tamaño es un factor que afecta al rendimiento que hay que tener presente.

Para ajustar el tamaño o la longitud de los datos definir los tipos con los siguientes tipos Numpy:
  • np.int8: (1 byte) - Para enteros entre -128 y 127.
  • np.int16: (2 bytes) - Para enteros entre -32768 y 32767.
  • np.int32: (4 bytes) - Para enteros entre -2147483648 y 2147483647.
  • np.int64: (8 bytes) - Para números enteros entre -9223372036854775808 y 9223372036854775807.

Si los arrays no van a contener enteros negativos se recomienda utilizar los tipos sin signo: np.uint8, np.uint16, np.uint32 y np.uint64. Así, aunque el espacio que ocupan en memoria sea el mismo, los enteros positivos pueden llegar a 255, 65535, 4294967295 y 18446744073709551615, respectivamente.

Para conocer el valor máximo o mínimo posible de un tipo:

np.iinfo(np.uint8).max  # 255                                                 
np.iinfo(np.uint8).min  # 0

Existe el problema de que cuando se asigna un dato numérico que está por encima o por debajo de los límites que impone el tipo de dato del array, numpy a priori no genera ninguna excepción y almacena un número que no se corresponde con el real. Por tanto, es conveniente elegir bien el tipo de dato en un array e implementar soluciones que eviten errores como el siguiente:

a = np.array([10, 10, 30, 110, 20], dtype=np.int8)  # Entero (int8)
a = a * 2
print(a)  # [ 20  20  60 -36  40] 

El valor 110 al multiplicarlo por 2 supera el límite superior para los enteros int8, que está en 127.


4. Con los tipos abreviados


La mejor opción muchas veces es la más simple. Los tipos abreviados son cadenas formadas por un carácter que representa el tipo y un número que expresa el tamaño o longitud del dato:

  • Booleanos: 'b1'.
  • Enteros: 'i1' (int8), 'i2' (int16), 'i4' (int32), 'i8' (int64).
  • Cadenas Unicode con distintas longitudes: 'u1', 'u2', 'u4', 'u8'.
  • Cadenas Bytes con distintas longitudes: 'S1', 'S2', 'S3', 'S4'.
  • Flotantes: 'f2' (float16), 'f4' (float32), 'f8' (float64).
  • Complejos: 'c8' (complex64), 'c16' (complex128).

a = np.array([0, 0, 1, 1, 0], dtype='b1')  # Booleano (bool)
print(a)  # [False False  True  True False]
a.dtype  # dtype('bool')

a = np.array([0, 0, 1, 1, 0], dtype='i1')  # Entero (int8)
print(a)  # [0 0 1 1 0]
a.dtype  # dtype('int8')

Para ampliar la información sobre tipos de datos que puede contener un array se recomienda visitar la página oficial de Numpy.



martes, 29 de octubre de 2019

Primeros pasos con Numpy




Introducción


Numpy es una biblioteca para Python que facilita el trabajo con arrays (vectores y matrices), un tipo de dato estructurado muy utilizado en análisis de datos, en informática científica y en el área del aprendizaje automático (learning machine).

Numpy permite declarar arrays con distintas dimensiones capaces de albergar gran cantidad de datos del mismo tipo y relacionados entre sí. Además, provee numerosos métodos para manipular los arrays; y para acceder a la información y procesarla de forma muy eficiente.

Básicamente, los datos (elementos) que puede contener un array son cadenas de caracteres, números enteros de distintos tamaños, números reales y booleanos, entre otros.

En un array unidimensional para acceder a dichos datos se utiliza un número (índice) que se corresponde con la posición que ocupa cada elemento en el array. En arrays de más de una dimensión se utilizan tantos indices como dimensiones o ejes existan.




El número de elementos que puede contener como máximo un array define su tamaño; que en arrays multidimensionales viene determinado por el producto del número máximo de elementos en cada eje o longitud de cada dimensión.

ndarray es la clase que permite crear objetos array en Numpy. Estos objetos recuerdan a los de la clase array del módulo array de la librería estándar de Python, aunque estos últimos solo permiten operar con arrays unidimensionales, cuentan con menos posibilidades para su procesamiento y tienen un rendimiento inferior.


Instalando Numpy


Instalar Numpy con el gestor de paquetes pip.

$ pip install numpy

Crear un entorno virtual con Numpy y IPython.

Como suele ser habitual se aconseja crear entornos virtuales para desarrollar cualquier tipo de proyecto Python. Para preparar y utilizar un entorno virtual venv con numpy y el entorno interactivo IPython para probar los ejemplos de esta guía para impacientes, seguir los siguientes pasos:

1. Crear un entorno virtual para un proyecto:

$ python3 -m venv proyecto1

2. Acceder al directorio del proyecto:

$ cd proyecto1

3. Activar el entorno virtual del proyecto:

$ source bin/activate

4. Instalar IPython y Numpy en el entorno virtual del proyecto:

$ pip install ipython
$ pip install numpy

5. Iniciar IPython para iniciar una sesión de trabajo con Numpy.

$ ipython

6. Terminar una sesión IPython

: quit

7. Desactivar el entorno virtual del proyecto

$ deactivate

Conocer la versión instalada de Numpy.

1. Con pip:

$ pip show numpy

Name: numpy
Version: 1.17.3
Summary: NumPy is the fundamental package for array with Python.
Home-page: https://www.numpy.org
Author: Travis E. Oliphant et al.
Author-email: None
License: BSD

2. En IPython

: import numpy as np
: np.version.version  # '1.17.3'

: np.__version__  # '1.17.3'

También, existen distribuciones Python como Anaconda y ActivePython que incluyen entre sus librerías a Numpy. Estas distribuciones son recomendables para desarrolladores que van a trabajar en proyectos relacionados con el análisis de datos porque proporcionan herramientas como Jupyter, Ipython, etc. y otros paquetes muy usados en este tipo de proyectos como Scipy, Pandas, matplotlib, etc.

Crear un entorno virtual con Miniconda.

La distribución ligera Miniconda que deriva de Anaconda es una buena alternativa para comenzar a dar los primeros pasos con Numpy. Para crear un entorno virtual después de su instalación seguir los siguientes pasos:

1. Crear entorno virtual en Miniconda:

$ conda create -n proyecto1

2. Activar entorno virtual:

$ conda activate proyecto1

3. Instalar Numpy y IPuthon

$ conda install numpy

$ conda install ipython

4. Desactivar entorno virtual:

$ conda deactivate


Declarando arrays con Numpy


Comenzar una sesión con Numpy.

Para comenzar una sesión con Numpy, activar el entorno virtual, iniciar IPython e importar el modulo Numpy con el alias np:

import numpy as np


array()


Declarar un array.

# Declarar un array con los números enteros del 1 al 5:

a = np.array([1, 2, 3, 4, 5])

# Acceder al objeto array:

a  # array([1, 2, 3, 4, 5])

# Obtener el tipo de objeto declarado:

type(a)  # numpy.ndarray

# Imprimir el array:

print(a)  # [1 2 3 4 5]

# Comprobar si existe un array Numpy llamado a:

if isinstance(a, np.ndarray):
    print('Existe')
else:
    print('No existe') 

# Declarar un array bidimensional (2x3) para datos de tipo float:

a = np.array([[1, 2, 3], [4, 5.8, 6]], dtype=float)
print(a)   

# [[1.  2.  3. ]
#  [4.  5.8 6. ]]

# Declarar un array tridimensional (2x2x2) para datos de tipo int:

a = np.array([[[1, 2], [3, 4]], [[5, 6], [7, 8]]], dtype=int)
print(a)

# [[[1 2]
#   [3 4]]
#
#  [[5 6]
#   [7 8]]]

# Declarar un array sin datos (vacío):

a = np.array([])
a  # array([], dtype=float64)
print(a)  # []


arange()


Declarar un array con una sucesión de valores.

# Declarar un array con 5 valores, del 0 al 4:

a = np.arange(5)
print(a)  # [0 1 2 3 4]
 
# Declarar un array con valores desde 1 hasta 10 con un 
# intervalo de 2:

a = np.arange(1, 10, 2)
print(a)  # [1 3 5 7 9]

# Declarar un array con valores desde 2 hasta 4:

a = np.arange(2, 4)
print(a)  # [2 3]


zeros()


Declarar un array con todos los elementos con el valor 0.

# Declarar array bidimensional (2x2) con todos los elementos con 0:

a = np.zeros((2, 2))
print(a)

# [[0. 0.]
#  [0. 0.]]


ones()


Declarar un array con todos los elementos con el valor 1.

# Declarar array bidimensional (3x2) con todos los elementos con 1:

a = np.ones((3, 2))
print(a)

# [[1. 1.]
#  [1. 1.]
#  [1. 1.]]


empty()


Declarar un array vacío.

# Declarar un array bidimensional (2x2) vacío.

a = np.empty((2, 2))
print(a)

# [[0. 0.]
#  [0. 0.]]

# El array se creará con los datos existentes en las direcciones
# de memoria.


random.randint()


Declarar un array con números enteros aleatorios.

# Declarar un array (3x3) con enteros aleatorios entre 1 y 9:

a = random.randint(1, 10, (3,3))
print(a)

# [[6 7 2]
#  [1 9 4]
#  [8 2 2]]


random.random()


Declarar un array con números aleatorios.

# Declarar un array de (3x3) con números aleatorios:

a = np.random.random((3,3))
print(a)

# [[0.46256707 0.05762619 0.84186372]
#  [0.37662455 0.9371823  0.02038264]
#  [0.24177232 0.45393929 0.33990566]]


identity()


Declarar un array con la matriz identidad.

Una matriz identidad es una matriz cuadrada donde todos sus elementos son ceros (0) excepto los elementos de la diagonal principal que son unos (1).

# Declarar un array (3x3) con la matriz identidad:

a = np.identity(3)
print(a)

# [[1. 0. 0.]
#  [0. 1. 0.]
#  [0. 0. 1.]]


eye()


Declarar un array con sus elementos con 0 y 1 en la diagonal principal.

# Declarar un array (3x3) con todos sus elementos con 0 y
# 1 en la diagonal principal.

a = np.eye(3)
print(a)

# [[1. 0. 0.]
#  [0. 1. 0.]
#  [0. 0. 1.]]

# Declarar un array (3x3) con todos sus elementos con 0 y 1 en
# la diagonal 1.

# k es un índice que representa la diagonal en la que los elementos 
# tendrán el valor 1: si k es 0 se trata de la diagonal principal; 
# si es un número positivo una diagonal superior y si es un número 
# negativo una inferior.

a = np.eye(3, k=1)
print(a)

# [[0. 1. 0.]
#  [0. 0. 1.]
#  [0. 0. 0.]]

# Declarar un array (3x5) con todos sus elementos con 0 y 1 en la 
# diagonal -2.

a = np.eye(3, 5, k=-2)
print(a)

# [[0. 0. 0. 0. 0.]
#  [0. 0. 0. 0. 0.]
#  [1. 0. 0. 0. 0.]]


full()


Declarar un array con todos los elementos con el mismo valor.

# Declarar un array (3x3) con todos los elementos con 5:

a = np.full((3, 3), 5)
print(a)

# [[5 5 5]
#  [5 5 5]
#  [5 5 5]]


asarray()


Declarar un array con los elementos de una lista o una tupla.

# Declarar un array con los elementos de una tupla:

tupla = (1, 2, 3, 4, 5)
a = np.asarray(tupla)
print(a)  # [1 2 3 4 5]

# Declarar un array (2x3) con los elementos de una lista:

lista = [[1, 2, 3], [4, 5, 6]]
a = np.asarray(lista)
print(a)

# [[1 2 3]
#  [4 5 6]]


full_like()


Declarar un array con dimensiones y forma iguales que otro array.

# Declarar un array con dimensiones y forma de otro array pero
# con otros valores:

matriz = np.array([(1, 2, 3), (4, 5, 6)])
a = np.full_like(matriz, -1)
print(a)

# [[-1 -1 -1]
#  [-1 -1 -1]]

Otras opciones: zeros_like() y ones_like()


frombuffer()


Declarar un array con enteros a partir de una cadena de bytes.

# Declarar un array con 12 enteros a partir de una cadena de bytes:

cadena = b'Python para impacientes'
a = np.frombuffer(cadena, count=12, dtype=np.int8)
print(a)  # [ 80 121 116 104 111 110  32 112  97 114  97  32]

# Declarar un array de cadenas de 1 byte a partir de una cadena.

cadena = b'Python para impacientes'
a = np.frombuffer(cadena, count=6, dtype='S1')
print(a)  # [b'P' b'y' b't' b'h' b'o' b'n']


linspace()


Declarar un array con valores equidistantes.

# Declarar un array de 5 elementos con valores entre 0 y 2:

a = np.linspace(0, 2, num=5)
print(a)  #  [0.  0.5 1.  1.5 2. ]


fromfunction()


Declarar un array a partir de los datos de una función.

# Declarar array a partir de los datos de una función lambda:

a = np.fromfunction(lambda x: x*x, (10,), dtype=int) 
print(a)  # [ 0  1  4  9 16 25 36 49 64 81]

# Declarar un array (5x5) a partir de los datos de una función.

a = np.fromfunction(lambda x, y: x-y-1, (5, 5), dtype=int) 
print(a)

# [[-1 -2 -3 -4 -5]
#  [ 0 -1 -2 -3 -4]
#  [ 1  0 -1 -2 -3]
#  [ 2  1  0 -1 -2]
#  [ 3  2  1  0 -1]]


fromiter()


Declarar un array con los valores devueltos por un iterador.

# Declarar vector con valores decrecientes devueltos por iterador:

iterador = [v for v in range(100,1,-3)]
a = np.fromiter(iterador, int, count=10)
print(a)  # [100  97  94  91  88  85  82  79  76  73]


fromstring()


Declarar un array a partir de una cadena de texto.

# Declarar vector con booleanos a partir los valores de una cadena:

a = np.fromstring('0,0,0,1,1,0,1', dtype=bool, sep=',')  #
print(a)  # [False False False  True  True False  True]


diag()


Declarar un array a partir de la diagonal de otro array.

# Declarar un array a partir de la diagonal principal de otro:

a = np.array([(1, 5, 10), (5, 10, 15), (10, 15, 20)])
diagonal = np.diag(a)  # array([ 1, 10, 20])
b = np.diag(diagonal)
print(b)

# [[ 1  0  0]
#  [ 0 10  0]
#  [ 0  0 20]]

# Declarar un array a partir de elementos de la diagonal -1:

a = np.array([(1, 5, 10), (5, 10, 15), (10, 15, 20)])
diagonal = np.diag(a, k=-1)  # array([ 5, 15])
b = np.diag(diagonal)
print(b)

# [[ 5  0]
#  [ 0 15]]


diagflat()


Declarar un array con los elementos de otro en la diagonal.

a = np.array([(0, 5), (5, 10)])
b = np.diagflat(a) 
print(b)

# [[ 0  0  0  0]
#  [ 0  5  0  0]
#  [ 0  0  5  0]
#  [ 0  0  0 10]]


tri()


Declarar array con diagonal principal e inferiores a 1 y resto a 0.

a = np.tri(4)
print(a)

# [[1. 0. 0. 0.]
#  [1. 1. 0. 0.]
#  [1. 1. 1. 0.]
#  [1. 1. 1. 1.]]


dtype={'names':(),'formats':()})


Declarar arrays con datos estructurados a partir de listas.

# Declarar listas con nombres y edades.

# Los datos de ambas listas están relacionados por su posición:
# la edad de María es 24 años, la de Carlos es 25, etc.

nombre = ['Maria', 'Carlos', 'Ana', 'Luisa']
edad = [24, 25, 35, 19]

# Declarar array vacío con dos campos:

datos = np.empty(4, dtype={'names':('nombre', 'edad'),
                           'formats':('U15', 'i4')})

# Asignar los datos de las listas a los campos del array:

datos['nombre'] = nombre
datos['edad'] = edad

# Obtener todos los nombres:

print(datos['nombre'])  # ['Maria' 'Carlos' 'Ana' 'Luisa']

# Obtener todas las edades:

print(datos['edad'])  # [24 25 35 19]

# Obtener los datos de la primera persona:

print(datos[0])  # ('Maria', 24)

# Obtener los datos de la última persona:

print(datos[-1])  # ('Luisa', 19)

# Obtener la primera edad:

print(datos[0]['edad'])  # 24

# Obtener array con todos los nombres con más de 24 años:

print(datos[datos['edad'] > 24]['nombre'])  # ['Carlos' 'Ana']


core.records.fromarrays()


Declarar un registro de arrays.

Permite operar con los elementos de varios arrays de distintos tipos agrupados en un nuevo tipo de objeto llamado registro que funciona como un array.

a = np.array(['a', 'b', 'c', 'd', 'e'])
b = np.array([1, 2, 3, 4, 5])
registro = np.core.records.fromarrays([a, b],names='a, b')
print(registro.a)  # ['a' 'b' 'c' 'd' 'e']
print(registro.b)  # [1 2 3 4 5]
print(registro[0])  # ('a', 1)
registro[2] = ('x', 0)
print(registro.a)  # ['a' 'b' 'x' 'd' 'e']
print(registro)  # [('a', 1) ('b', 2) ('x', 0) ('d', 4) ('e', 5)]

La próxima entrada la dedicamos a los tipos de arrays que existen atendiendo a los datos que pueden contener y a las formas que existen para definirlos.