<jupyter_start><jupyter_text>Numpy Numpy proporciona un nuevo contenedor de datos a Python, los `ndarray`s, además de funcionalidad especializada para poder manipularlos de forma eficiente.Hablar de manipulación de datos en Python es sinónimo de Numpy y prácticamente todo el ecosistema científico de Python está construido sobre Numpy. Digamos que Numpy es el ladrillo que ha permitido levantar edificios tan sólidos como Pandas, Matplotlib, Scipy, scikit-learn,...**Índice*** [¿Por qué un nuevo contenedor de datos?](%C2%BFPor-qu%C3%A9-un-nuevo-contenedor-de-datos?)* [Creación de `numpy` arrays](Creaci%C3%B3n-de-numpy-arrays)* [Operaciones disponibles más típicas](Operaciones-disponibles-más-típicas-que-podemos-hacer-con-un-numpy-array)* [Indexación](Indexaci%C3%B3n)* [Agregación](Agregación)* [Cambios de forma](Cambios-de-forma)* [Resumen](Resumen)  ¿Por qué un nuevo contenedor de datos? En Python, disponemos, de partida, de diversos contenedores de datos, listas, tuplas, diccionarios, conjuntos,..., ¿por qué añadir uno más?.¡Por conveniencia!, a pesar de la pérdida de flexibilidad. Es una solución de compromiso.* Uso de memoria más eficiente: Por ejemplo, una lista puede contener distintos tipos de objetos lo que provoca que Python deba guardar información del tipo de cada elemento contenido en la lista. Por otra parte, un `ndarray` contiene tipos homogéneos, es decir, todos los elementos son del mismo tipo, por lo que la información del tipo solo debe guardarse una vez independientemente del número de elementos que tenga el `ndarray`.![arrays_vs_listas](../../images/03_01_array_vs_list.png)***(imagen por <NAME> y extraída [de GitHub](https://github.com/jakevdp/PythonDataScienceHandbook)).**** Más rápido: Por ejemplo, en una lista que consta de elementos con diferentes tipos Python debe realizar trabajos extra para saber si los tipos son compatibles con las operaciones que estamos realizando. Cuando trabajamos con un `ndarray` ya podemos saber eso de partida y podemos tener operaciones más eficientes (además de que mucha funcionalidad está programada en C, C++, Cython, Fortran).* Operaciones vectorizadas* Funcionalidad extra: Muchas operaciones de álgebra lineal, transformadas rápidas de Fourier, estadística básica, histogramas,...* Acceso a los elementos más conveniente: Indexación más avanzada que con los tipos normales de Python* ... Uso de memoria<jupyter_code># AVISO: SYS.GETSYZEOF NO ES FIABLE

lista = list(range(5_000_000))
arr = np.array(lista, dtype=np.uint32)
print("5 millones de elementos")
print(sys.getsizeof(lista))
print(sys.getsizeof(arr))

print()

lista = list(range(100))
arr = np.array(lista, dtype=np.uint8)
print("100 elementos")
print(sys.getsizeof(lista))
print(sys.getsizeof(arr))<jupyter_output><empty_output><jupyter_text>Velocidad de operaciones<jupyter_code>a = list(range(1_000_000))
%timeit sum(a)
print(sum(a))
a = np.array(a)
%timeit np.sum(a)
print(np.sum(a))<jupyter_output><empty_output><jupyter_text>Operaciones vectorizadas<jupyter_code># Suma de dos vectores elemento a elemento
a = [1, 1, 1]
b = [3, 4, 3]
print(a + b)
print('Fail')
# Suma de dos vectores elemento a elemento
a = np.array([1, 1, 1])
b = np.array([3, 4, 3])
print(a + b)
print('\o/')<jupyter_output><empty_output><jupyter_text>Funcionalidad más conveniente<jupyter_code># suma acumulada
a = list(range(100))
print([sum(a[:i+1]) for i in a])

a = np.array(a)
print(a.cumsum())<jupyter_output><empty_output><jupyter_text>Acceso a elementos más conveniente<jupyter_code>a = [[11, 12, 13],
     [21, 22, 23],
     [31, 32, 33]]
print('acceso a la primera fila: ', a[0])
print('acceso a la primera columna: ', a[:][0], ' Fail!!!')
a = np.array(a)
print('acceso a la primera fila: ', a[0])
print('acceso a la primera columna: ', a[:,0], ' \o/')<jupyter_output><empty_output><jupyter_text>... Recapitulando un poco.***Los `ndarray`s son contenedores multidimensionales, homogéneos con elementos de tamaño fijo, de dimensión predefinida.***  Creación de numpy arrays ![Creacion de arrays](../../images/03_01a_arraycreation.png)(Imagen extraida de [aquí](http://jalammar.github.io/visual-numpy/)). Podemos crear numpy arrays de muchas formas.![Creacion de arrays](../../images/03_01b_arraycreation.png)(Imagen extraida de [aquí](http://jalammar.github.io/visual-numpy/)). * Rangos numéricos`np.arange`, `np.linspace`, `np.logspace`* Datos homogéneos`np.zeros`, `np.ones`* Elementos diagonales`np.diag`, `np.eye`* A partir de otras estructuras de datos ya creadas`np.array`* A partir de otros numpy arrays`np.empty_like`* A partir de ficheros`np.loadtxt`, `np.genfromtxt`,...* A partir de un escalar`np.full`, `np.tile`,...* A partir de valores aleatorios`np.random.randint`, `np.random.randint`, `np.random.randn`,...... ![Creacion de arrays](../../images/03_01c_arraycreation.png)(Imagen extraida de [aquí](http://jalammar.github.io/visual-numpy/)). Algunos ejemplos:<jupyter_code>a = np.arange(10) # similar a range pero devuelve un ndarray en lugar de un objeto range
print(a)
a = np.linspace(0, 1, 101)
print(a)
a_i = np.zeros((2, 3), dtype=np.int)
a_f = np.zeros((2, 3))
print(a_i)
print(a_f)
a = np.eye(3)
print(a)<jupyter_output><empty_output><jupyter_text>Obviamente, no solo trabajamos con datos en una dimensión y los *arrays* me permiten tener más dimensiones: ![Creacion de arrays](../../images/03_01aa_arraycreation.png)(Imagen extraida de [aquí](http://jalammar.github.io/visual-numpy/)). ![Creacion de arrays](../../images/03_01bb_arraycreation.png)(Imagen extraida de [aquí](http://jalammar.github.io/visual-numpy/)).<jupyter_code>a = np.array(
    (
        (1, 2, 3, 4, 5, 6),
        (10, 20, 30, 40, 50, 60)
    ), 
    dtype=np.float
)
print(a)
np.full((5, 5), -999)<jupyter_output><empty_output><jupyter_text>O, incluso, más dimensiones: ![Creacion de arrays](../../images/03_01aaa_arraycreation.png)(Imagen extraida de [aquí](http://jalammar.github.io/visual-numpy/)). ![Creacion de arrays](../../images/03_01bbb_arraycreation.png)(Imagen extraida de [aquí](http://jalammar.github.io/visual-numpy/)).     Referencias:    array creation    routines for array creation **Practicando****[INCISO: Recordad que siempre podéis usar `help`, `?`, `np.lookfor`,..., para obtener más información]**.<jupyter_code>help(np.sum)
np.rad2deg?
np.lookfor("create array")<jupyter_output><empty_output><jupyter_text>Ved un poco como funciona `np.repeat`, `np.empty_like`,...<jupyter_code># Play area


%load ../../solutions/03_01_np_array_creacion.py<jupyter_output><empty_output><jupyter_text>Operaciones disponibles más típicas que podemos hacer con un numpy array  Aritmética Por ejemplo, una operación de suma de dos *arrays* de una dimensión: ![aritmética](../../images/03_01d_arithmetic.png)(Imagen extraida de [aquí](http://jalammar.github.io/visual-numpy/)). ![aritmética](../../images/03_01e_arithmetic.png)(Imagen extraida de [aquí](http://jalammar.github.io/visual-numpy/)). Hemos hecho una suma pero podemos hacer restas, multiplicaciones, divisiones,... ![aritmética](../../images/03_01f_arithmetic.png)(Imagen extraida de [aquí](http://jalammar.github.io/visual-numpy/)). Se puede generalizar sin necesidad de ocupar memoria. Por ejemplo: ![aritmética](../../images/03_01g_arithmetic.png)(Imagen extraida de [aquí](http://jalammar.github.io/visual-numpy/)). La operación suma pero con más dimensiones sería así: ![aritmética](../../images/03_01dd_arithmetic.png)(Imagen extraida de [aquí](http://jalammar.github.io/visual-numpy/)). Y, de igual forma, también podemos generalizar: ![aritmética](../../images/03_01ee_arithmetic.png)(Imagen extraida de [aquí](http://jalammar.github.io/visual-numpy/)). Probad vosotros. Cread un par (o 3 o 4 o 5,...) de *arrays* como hemos visto en la sección anterior y haced alguna operación con ellos:<jupyter_code># Play area<jupyter_output><empty_output><jupyter_text>Referencias:    Quick start tutorial     Referencias:    Basic broadcasting    Broadcasting more in depth  Indexación Si ya has trabajado con indexación en estructuras de Python, como listas, tuplas o strings, la indexación en Numpy te resultará muy familiar. Por ejemplo, por hacer las cosas sencillas, vamos a crear un `ndarray` de 1D: ![indexación](../../images/03_01h_indexing.png)(Imagen extraida de [aquí](http://jalammar.github.io/visual-numpy/)). Algo un pelín más complejo. Ejecutad las dos siguientes celdas e indicad si entendéis lo que está pasando en la segunda:<jupyter_code>a = np.arange(10, dtype=np.uint8)
print(a)
print(a[:]) # para acceder a todos los elementos
print(a[:-1]) # todos los elementos menos el último
print(a[1:]) # todos los elementos menos el primero
print(a[::2]) # el primer, el tercer, el quinto,..., elemento
print(a[3]) # el cuarto elemento
print(a[-1:-5:-1]) # ¿?<jupyter_output><empty_output><jupyter_text>Para *ndarrays* de una dimensión es exactamente igual que si usásemos listas o tuplas de Python:* Primer elemento tiene índice 0* Los índices negativos empiezan a contar desde el final* slices/rebanadas con `[start:stop:step]` Con un `ndarray` de más dimensiones las cosas ya cambian con respecto a Python puro: ![indexación](../../images/03_01hh_indexing.png)(Imagen extraida de [aquí](http://jalammar.github.io/visual-numpy/)). Lo mismo que antes, intentad entender lo siguiente:<jupyter_code>a = np.random.randn(10, 2)
print(a)
print(a[1]) # ¿Qué nos dará esto?
print(a[1, 1]) # Si queremos acceder a un elemento específico hay que dar su posición completa en el ndarray
print(a[::3, 1])<jupyter_output><empty_output><jupyter_text>Vamos a considerar el siguiente numpy array y vamos a trabajar un poco el *slicing*<jupyter_code>a = np.arange(40).reshape(5, 8)
print(a)<jupyter_output><empty_output><jupyter_text>Si tenemos dimensiones mayores a 1 es parecido a las listas pero los índices se separan por comas para las nuevas dimensiones.(imagen extraída de [aquí](https://github.com/gertingold/euroscipy-numpy-tutorial))<jupyter_code>a[2, -3]<jupyter_output><empty_output><jupyter_text>Para obtener más de un elemento hacemos *slicing* para cada eje:(imagen extraída de [aquí](https://github.com/gertingold/euroscipy-numpy-tutorial))<jupyter_code>a[:3, :5]<jupyter_output><empty_output><jupyter_text>Jugamos de nuevo!!! ¿Cómo podemos conseguir los elementos señalados en esta imagen?(imagen extraída de [aquí](https://github.com/gertingold/euroscipy-numpy-tutorial))<jupyter_code># ¿?<jupyter_output><empty_output><jupyter_text>¿Cómo podemos conseguir los elementos señalados en esta imagen?(imagen extraída de [aquí](https://github.com/gertingold/euroscipy-numpy-tutorial))<jupyter_code># ¿?<jupyter_output><empty_output><jupyter_text>¿Cómo podemos conseguir los elementos señalados en esta imagen?(imagen extraída de [aquí](https://github.com/gertingold/euroscipy-numpy-tutorial))<jupyter_code># ¿?<jupyter_output><empty_output><jupyter_text>¿Cómo podemos conseguir los elementos señalados en esta imagen?(imagen extraída de [aquí](https://github.com/gertingold/euroscipy-numpy-tutorial))<jupyter_code># ¿?<jupyter_output><empty_output><jupyter_text>Soluciones a lo anterior:<jupyter_code>%load ../../solutions/03_04_array_indexing.py<jupyter_output><empty_output><jupyter_text>**Fancy indexing** Con *fancy indexing* podemos hacer cosas tan variopintas como: (imágenes extraídas de [aquí](https://github.com/gertingold/euroscipy-numpy-tutorial)) Es decir, podemos indexar usando `ndarray`s de booleanos ó usando listas de índices para extraer elementos concretos de una sola vez.**WARNING: En el momento que usamos *fancy indexing* nos devuelve un nuevo *ndarray* que no tiene porque conservar la estructura original.**     Referencias:    array indexing    indexing arrays  Agregación Podemos hacer operaciones sobre el propio *array* y extraer [información agregada](https://en.wikipedia.org/wiki/Aggregate_function) (información que resume, agrupa o extrae información o parte de la misma): ![agregación](../../images/03_01i_aggregation.png)(Imagen extraida de [aquí](http://jalammar.github.io/visual-numpy/)). ![agregación](../../images/03_01ii_aggregation.png)(Imagen extraida de [aquí](http://jalammar.github.io/visual-numpy/)). ![agregación](../../images/03_01iii_aggregation.png)(Imagen extraida de [aquí](http://jalammar.github.io/visual-numpy/)). Por ejemplo, sacad la suma de un array de 5x3 (5 filas y 3 columnas):<jupyter_code># Play area<jupyter_output><empty_output><jupyter_text>Cambios de forma Podemos crear *arrays* que tengan determinada forma pero quizá nos interesa cambiar esa forma para realizar una operación donde esa forma será más conveniente. Por ejemplo, podríamos girar un *array* bidimensional usando la transpuesta: ![transponer](../../images/03_01j_transpose.png)(Imagen extraida de [aquí](http://jalammar.github.io/visual-numpy/)). O podemos cambiar la forma usando el método `.reshape`: ![cambio de forma](../../images/03_01k_reshape.png)(Imagen extraida de [aquí](http://jalammar.github.io/visual-numpy/)). Intentad aplanar un *array* usando el método `.ravel`:<jupyter_code>a = np.random.random((5, 2))
a.ravel?
# Play area<jupyter_output><empty_output>
{'fraction_non_alphanumeric': 0.13411657003356728, 'fraction_numerical': 0.017927982911199267, 'mean_word_length': 5.781686497672013, 'pattern_counts': {'":': 0, '<': 90, '<?xml version=': 0, '>': 90, 'https://': 9, 'lorem ipsum': 0, 'www.': 0, 'xml': 0}, 'pii_count': 0, 'substrings_counts': 0, 'word_list_counts': {'cursed_substrings.json': 1, 'profanity_word_list.json': 0, 'sexual_word_list.json': 0, 'zh_pornsignals.json': 0}}
{'chain_length': '22', 'dir': 'jupyter-structured-clean-dedup', 'id': '480671', 'license': 'MIT', 'n_tokens_mistral': 4904, 'n_tokens_neox': 4681, 'n_words': 1238, 'path': 'notebooks/03_numpy/03-Numpy-FTW2019.ipynb', 'repo_name': 'PyDataMallorca/FTW2019_Introduccion_a_data_science_en_Python', 'size': '13117'}