Comprender los tipos de datos en Python

Comprender los tipos
de datos en Python



La ciencia y la computación eficaces basadas en datos requieren comprender cómo se almacenan y manipulan los datos. Esta sección describe y contrasta cómo se manejan las matrices de datos en el lenguaje Python mismo, y cómo NumPy mejora esto. Comprender esta diferencia es fundamental para comprender gran parte del material a lo largo del resto del libro.


Los usuarios de Python a menudo se sienten atraídos por su facilidad de uso, una parte de la cual es la escritura dinámica. Mientras que un lenguaje de tipado estático como C o Java requiere que cada variable se declare explícitamente, un lenguaje de tipado dinámico como Python omite esta especificación. Por ejemplo, en C puede especificar una operación en particular de la siguiente manera:


/* C code */
int result = 0;
for(int i=0; i<100; i++){
    result += i;
}


Mientras que en Python, la operación equivalente podría escribirse de esta manera:


# Python code
result = 0
for i in range(100):
    result += i


Observe la principal diferencia: en C, los tipos de datos de cada variable se declaran explícitamente, mientras que en Python los tipos se infieren dinámicamente. Esto significa, por ejemplo, que podemos asignar cualquier tipo de dato a cualquier variable:


# Python code
x = 4
x = "four"


Aquí hemos cambiado el contenido de x de un número entero a una cadena. Lo mismo en C conduciría (dependiendo de la configuración del compilador) a un error de compilación u otras consecuencias no intencionadas:


/* C code */
int x = 4;
x = "four";  // FAILS


Este tipo de flexibilidad es una pieza que hace que Python y otros lenguajes escritos dinámicamente sean convenientes y fáciles de usar. Comprender cómo funciona esto es un aprendizaje importante para analizar datos de manera eficiente y efectiva con Python. Pero lo que esta flexibilidad de tipos también apunta es el hecho de que las variables de Python son más que solo su valor; también contienen información adicional sobre el tipo de valor. Exploraremos esto más en las secciones que siguen.


Un entero de Python es más que un entero

La implementación estándar de Python está escrita en C. Esto significa que cada objeto de Python es simplemente una estructura en C inteligentemente disfrazada, que contiene no solo su valor, sino también otra información. Por ejemplo, cuando definimos un número entero en Python, como x = 10000, x no es solo un número entero "sin formato". En realidad, es un puntero a una estructura C compuesta, que contiene varios valores. Mirando a través del código fuente de Python 3.4, encontramos que la definición de tipo entero (largo) efectivamente se ve así (una vez que se expanden las macros C):


struct _longobject {
    long ob_refcnt;
    PyTypeObject *ob_type;
    size_t ob_size;
    long ob_digit[1];
};


Un solo entero en Python 3.4 en realidad contiene cuatro piezas:


  • ob_refcnt, un recuento de referencias que ayuda a Python a manejar silenciosamente la asignación y desasignación de memoria.
  • ob_type, que codifica el tipo de variable.
  • ob_size, que especifica el tamaño de los siguientes miembros de datos.
  • ob_digit, que contiene el valor entero real que esperamos que represente la variable de Python.


Esto significa que existe una sobrecarga al almacenar un número entero en Python en comparación con un número entero en un lenguaje compilado como C, como se ilustra en la siguiente figura:




Aquí PyObject_HEAD es la parte de la estructura que contiene el recuento de referencias, el código de tipo y otras piezas mencionadas anteriormente.


Observe la diferencia aquí: un entero C es esencialmente una etiqueta para una posición en la memoria cuyos bytes codifican un valor entero. Un entero de Python es un puntero a una posición en la memoria que contiene toda la información del objeto de Python, incluidos los bytes que contienen el valor entero. Esta información adicional en la estructura de enteros de Python es lo que permite que Python se codifique de manera tan libre y dinámica. Sin embargo, toda esta información adicional en los tipos de Python tiene un costo, que se vuelve especialmente evidente en estructuras que combinan muchos de estos objetos.


Una lista de Python es más que una lista

Consideremos ahora qué sucede cuando usamos una estructura de datos de Python que contiene muchos objetos de Python. El contenedor de elementos múltiples mutable estándar en Python es la lista. Podemos crear una lista de enteros de la siguiente manera:


In [1]:
L = list(range(10))
L
Out[1]:
[0, 1, 2, 3, 4, 5, 6, 7, 8, 9]


In [2]:
type(L[0])
Out[2]:
int


O, de manera similar, una lista de cadenas:


In [3]:
L2 = [str(c) for c in L]
L2
Out[3]:
['0', '1', '2', '3', '4', '5', '6', '7', '8', '9']


In [4]:
type(L2[0])
Out[4]:
str


Debido a la escritura dinámica de Python, incluso podemos crear listas heterogéneas:


In [5]:
L3 = [True, "2", 3.0, 4]
[type(item) for item in L3]
Out[5]:
[bool, str, float, int]


Pero esta flexibilidad tiene un costo: para permitir estos tipos flexibles, cada elemento de la lista debe contener su propia información de tipo, recuento de referencias y otra información, es decir, cada elemento es un objeto Python completo. En el caso especial de que todas las variables sean del mismo tipo, gran parte de esta información es redundante: puede ser mucho más eficiente almacenar datos en una matriz de tipo fijo. La diferencia entre una lista de tipo dinámico y una matriz de tipo fijo (estilo NumPy) se ilustra en la siguiente figura:




En el nivel de implementación, la matriz contiene esencialmente un único puntero a un bloque de datos contiguo. La lista de Python, por otro lado, contiene un puntero a un bloque de punteros, cada uno de los cuales, a su vez, apunta a un objeto completo de Python como el entero de Python que vimos anteriormente. Una vez más, la ventaja de la lista es la flexibilidad: debido a que cada elemento de la lista es una estructura completa que contiene tanto datos como información de tipo, la lista puede llenarse con datos de cualquier tipo deseado. Los arreglos de estilo NumPy de tipo fijo carecen de esta flexibilidad, pero son mucho más eficientes para almacenar y manipular datos.


Matrices de tipo fijo en Python

Python ofrece varias opciones diferentes para almacenar datos en búferes de datos de tipo fijo eficientes. El módulo array incorporado (disponible desde Python 3.3) se puede utilizar para crear matrices densas de un tipo uniforme:


In [6]:
import array
L = list(range(10))
A = array.array('i', L)
A
Out[6]:
array('i', [0, 1, 2, 3, 4, 5, 6, 7, 8, 9])


Aquí 'i' es un código de tipo que indica que los contenidos son números enteros.


Sin embargo, mucho más útil es el objeto ndarray del paquete NumPy. Mientras que el objeto array de Python proporciona un almacenamiento eficiente de datos basados en arreglos, NumPy se suma a estas operaciones eficientes en esos datos. Exploraremos estas operaciones en secciones posteriores; aquí demostraremos varias formas de crear una matriz NumPy.


Comenzaremos con la importación estándar de NumPy, bajo el alias np:


In [7]:
import numpy as np


Creación de matrices a partir
de listas de Python

Primero, podemos usar np.array para crear matrices a partir de listas de Python:


In [8]:
# integer array:
np.array([1, 4, 2, 5, 3])
Out[8]:
array([1, 4, 2, 5, 3])


Recuerde que, a diferencia de las listas de Python, NumPy está restringido a matrices que contienen el mismo tipo. Si los tipos no coinciden, NumPy se convertirá en upcast si es posible (aquí, los enteros se convertirán en punto flotante):


In [9]:
np.array([3.14, 4, 2, 3])
Out[9]:
array([ 3.14,  4.  ,  2.  ,  3.  ])


Si queremos establecer explícitamente el tipo de datos de la matriz resultante, podemos usar la palabra clave dtype:


In [10]:
np.array([1, 2, 3, 4], dtype='float32')
Out[10]:
array([ 1.,  2.,  3.,  4.], dtype=float32)


Finalmente, a diferencia de las listas de Python, las matrices NumPy pueden ser explícitamente multidimensionales; aquí hay una forma de inicializar una matriz multidimensional usando una lista de listas:


In [11]:
# nested lists result in multi-dimensional arrays
np.array([range(i, i + 3) for i in [2, 4, 6]])
Out[11]:
array([[2, 3, 4],
       [4, 5, 6],
       [6, 7, 8]])


Las listas internas se tratan como filas de la matriz bidimensional resultante.


Crear matrices desde cero

Especialmente para arreglos más grandes, es más eficiente crear arreglos desde cero usando rutinas integradas en NumPy. A continuación se muestran varios ejemplos:


In [12]:
# Create a length-10 integer array filled with zeros
np.zeros(10, dtype=int)
Out[12]:
array([0, 0, 0, 0, 0, 0, 0, 0, 0, 0])


In [13]:
# Create a 3x5 floating-point array filled with ones
np.ones((3, 5), dtype=float)
Out[13]:
array([[ 1.,  1.,  1.,  1.,  1.],
       [ 1.,  1.,  1.,  1.,  1.],
       [ 1.,  1.,  1.,  1.,  1.]])


In [14]:
# Create a 3x5 array filled with 3.14
np.full((3, 5), 3.14)
Out[14]:
array([[ 3.14,  3.14,  3.14,  3.14,  3.14],
       [ 3.14,  3.14,  3.14,  3.14,  3.14],
       [ 3.14,  3.14,  3.14,  3.14,  3.14]])


In [15]:
# Create an array filled with a linear sequence
# Starting at 0, ending at 20, stepping by 2
# (this is similar to the built-in range() function)
np.arange(0, 20, 2)
Out[15]:
array([ 0,  2,  4,  6,  8, 10, 12, 14, 16, 18])


In [16]:
# Create an array of five values evenly spaced between 0 and 1
np.linspace(0, 1, 5)
Out[16]:
array([ 0.  ,  0.25,  0.5 ,  0.75,  1.  ])


In [17]:
# Create a 3x3 array of uniformly distributed
# random values between 0 and 1
np.random.random((3, 3))
Out[17]:
array([[ 0.99844933,  0.52183819,  0.22421193],
       [ 0.08007488,  0.45429293,  0.20941444],
       [ 0.14360941,  0.96910973,  0.946117  ]])


In [18]:
# Create a 3x3 array of normally distributed random values
# with mean 0 and standard deviation 1
np.random.normal(0, 1, (3, 3))
Out[18]:
array([[ 1.51772646,  0.39614948, -0.10634696],
       [ 0.25671348,  0.00732722,  0.37783601],
       [ 0.68446945,  0.15926039, -0.70744073]])


In [19]:
# Create a 3x3 array of random integers in the interval [0, 10)
np.random.randint(0, 10, (3, 3))
Out[19]:
array([[2, 3, 4],
       [5, 7, 8],
       [0, 5, 0]])


In [20]:
# Create a 3x3 identity matrix
np.eye(3)
Out[20]:
array([[ 1.,  0.,  0.],
       [ 0.,  1.,  0.],
       [ 0.,  0.,  1.]])


In [21]:
# Create an uninitialized array of three integers
# The values will be whatever happens to already exist at that memory location
np.empty(3)
Out[21]:
array([ 1.,  1.,  1.])


Tipos de datos estándar de NumPy

Las matrices NumPy contienen valores de un solo tipo, por lo que es importante tener un conocimiento detallado de esos tipos y sus limitaciones. Debido a que NumPy está construido en C, los tipos serán familiares para los usuarios de C, Fortran y otros lenguajes relacionados.


Los tipos de datos estándar de NumPy se enumeran en la siguiente tabla. Tenga en cuenta que al construir una matriz, se pueden especificar mediante una cadena:


np.zeros(10, dtype='int16')


O usando el objeto NumPy asociado:


np.zeros(10, dtype=np.int16)


Tipo de datos Descripción
bool_ Booleano (Verdadero o Falso) almacenado como un byte
int_ Tipo de entero predeterminado (igual que C long; normalmente int64 o int32)
intc Identical to C int (normally int32 o int64)
intp Número entero utilizado para la indexación (igual que C ssize_t; normalmente int32 o int64)
int8 Byte (-128 a 127)
int16 Entero (-32768 a 32767)
int32 Entero (-2147483648 a 2147483647)
int64 Entero (-9223372036854775808 a 9223372036854775807)
uint8 Entero sin signo (0 a 255)
uint16 Entero sin signo (0 a 65535)
uint32 Entero sin signo (0 a 4294967295)
uint64 Entero sin signo (0 a 18446744073709551615)
float_ Taquigrafía para float64.
float16 Flotante de media precisión: bit de signo, exponente de 5 bits, mantisa de 10 bits
float32 Flotador de precisión simple: bit de signo, exponente de 8 bits, mantisa de 23 bits
float64 Flotante de doble precisión: bit de signo, exponente de 11 bits, mantisa de 52 bits
complex_ Abreviatura de complex128.
complex64 Número complejo, representado por dos flotantes de 32 bits
complex128 Número complejo, representado por dos flotantes de 64 bits


Es posible una especificación de tipo más avanzada, como especificar números endian grandes o pequeños; para obtener más información, consulte la documentación de NumPy. NumPy también admite tipos de datos compuestos, que se tratarán en Datos estructurados: matrices estructuradas de NumPy.

JeshuaNomics

JeshuaNomics - DataScience es una web de divulgación donde se puede encontrar material formativo en ciencia de datos y programación estadística (R, Python y SQL).

Publicar un comentario (0)
Artículo Anterior Artículo Siguiente