<jupyter_start><jupyter_text>Table of Contents* [1. El módulo NumPy](1.-El-módulo-NumPy)	* [1.1 Creación de arrays](1.1-Creación-de-arrays)	* [1.2 Operaciones](1.2-Operaciones)	* [1.3 Acceso y recorrido](1.3-Acceso-y-recorrido)		* [1.3.1 Arrays de una dimensión](1.3.1-Arrays-de-una-dimensión)		* [1.3.2 Arrays bidimensionales](1.3.2-Arrays-bidimensionales)	* [1.4 Uso de máscaras para selección de partes de un array](1.4-Uso-de-máscaras-para-selección-de-partes-de-un-array)	* [1.5 Métodos matemáticos y estadisticos](1.5-Métodos-matemáticos-y-estadisticos)   El módulo NumPy El módulo __NumPy__ (Numerical Python) es uno de los paquetes fundamentales en el contexto de la computación científica. Pproporciona funciones y rutinas matemáticas para la manipulación de __arrays y matrices de datos numéricos__ de una forma eficiente. __NumPy__ proporciona:* El objeto __ndarray__: Un array multidimensional que permite realizar operaciones aritméticas sobre vectores muy eficientes. Es limilar a las listas pero añadiendo más eficiencia.* Colección de funciones matemáticas muy eficientes que operan sobre  vectores (ndarrays) sin necesidad de escribir bucles (for o  while). Son más eficientes y rápidas que las operaciones sobre listas.* Operaciones del álgebra lineal y genereación de números aleatorios.El módulo __SciPy__ extiende la funcionalidad de __NumPy__ con una colección de algoritmos matemáticos (minimización, transformada de Fourier, regresión, ...).<jupyter_code># Para usar NumPy lo primero que debemos hacer es importarlo

import numpy as np        # lo importamos con un alias<jupyter_output><empty_output><jupyter_text>Creación de arrays Existen varias formas para crear un array. * La forma más sencilla de crear un array es utilizando la función __array__ y una lista de objetos, que pueden ser otros arrays:<jupyter_code>v = np.array( [1,2,3, 6, 0, 2] )
v
a = np.array( [[2,3,4], [5, 6, 7]] )
a<jupyter_output><empty_output><jupyter_text>* Crear un array mediante la función __arange__. La función __arange__ crea un objeto de tipo __ndarray__.<jupyter_code># a diferencia de 'range', se pueden usar float 
a = np.arange( 0 , 5 , .5)     # números del 0 al 10(excluído) con paso 0.5
print(a)<jupyter_output>[0.  0.5 1.  1.5 2.  2.5 3.  3.5 4.  4.5]<jupyter_text>* Creación con datos aleatorios mediante la función __rand__ del módulo __Random__. La función __rand__ devuelve un número aleatorio procedente de una distribución uniforme en el intervalo [0,1).<jupyter_code># random es un submodulo dentro de numpy
a1 = np.random.rand(9)    # genera 10 números aleatorios dist.uniforme entre el 0 y el 1
a1<jupyter_output><empty_output><jupyter_text>También podemos crear arrays de dos dimensiones:<jupyter_code># creacion de una matriz con distribucion uniforme [0,1)
a2 = np.random.rand(3, 4)     # valores aleatorios - 3 filas, 4 columnas
a2<jupyter_output><empty_output><jupyter_text>Operaciones Los operadores aritméticos aplicados a arrays, __se aplican elemento a elemento__. Permite realizar operaciones sobre los arrays multidimensionales como si se tratara de operaciones sobre escales:<jupyter_code>a = np.array( [[2,3,4], [5, 6, 7]] )
a
# multiplica cada elemento del array, python le hace broadcast al '10'
a * 10<jupyter_output><empty_output><jupyter_text>__Operaciones aritméticas__Operaciones suma, resta, multiplicación, etc...Estas operaciones utilizan los operadores tradicionales, no se incluyen las operaciones matriciales las cualesnecesitan acceder por objetos<jupyter_code># para operar los arrays deben tener las mismas dimensiones o ser capaces de hacer broadcast (extender)
a = np.array([1,2,3], float) # el segundo argumento fuerza el tipado
b = np.array([5,2,6], float)
a + b
a - b<jupyter_output><empty_output><jupyter_text>>  Propiedades de los objetos __ndarray__:> > - La propiedad `shape` que indica las dimensiones del array.> - La propiedad `dtype` indica el tipo de los elementos almacenados en el array.<jupyter_code>a.shape, a.dtype<jupyter_output><empty_output><jupyter_text>__Funciones universales__Se trata de funciones que actúan sobre cada uno de los elementos de un array.<jupyter_code># funciones universales
array = np.array([-5, 60, -9])
b = np.abs(array) # algunas operaciones se realizan por modulo
b, b.max(), b.min() # otras operaciones se realizan por objeto
b = np.square(array)
b
c = np.sqrt(b)  
c
np.min(c)
np.maximum(a,b) # devuelve un array con el maximo en cada posicion entre ambos array argumento<jupyter_output><empty_output><jupyter_text>Acceso y recorrido  Arrays de una dimensión Cuando trabajamos con arrays de una dimensión, el acceso a los elementos se realiza de forma similar a como se hace en el caso de listas o tuplas de elementos.<jupyter_code>arr = np.arange(2, 20)
arr
len(arr)
arr[0]<jupyter_output><empty_output><jupyter_text>* Indexaciones iguales que en listas, podemos crear sub-arrays igual que con las sub-listasAl jugar con sub-arrays asignandolos a otras variables, se utiliza referencia por lo tanto si se modifica la nueva variable, se modifica el array original del cual se obtuvo el sub-arrayPara evitar que compartan memoria y sean dos arrays diferentes, se realiza una copia con ``c = a.copy()``<jupyter_code>arr[::-1]
arr[1::3]
arr[-2:]<jupyter_output><empty_output><jupyter_text>__Una diferencia importante con las listas__, es que las particiones de un ndarray mediante la notación [inicio:fin:paso] son vistas del array original. Todos los cambios realizados en las vistas, se reflejan en el array original:<jupyter_code># Las siguientes operaciones no las podriamos realizar de esta forma en listas
b = arr[-2:] # b es asignado a los dos ultimos elementos de arr
b[::] = 0 # asigna '0' a todos los elementos de b ya que indexa con [::] que es equivalente a todo el array
arr<jupyter_output><empty_output><jupyter_text>Arrays bidimensionales El acceso a los elementos de un array bidimensional, se realiza indicando los índices separados por una coma.<jupyter_code>b = np.array([[ 0,  1,  2,  3],
              [10, 11, 12, 13],
              [20, 21, 22, 23],
              [30, 31, 32, 33],
              [40, 41, 42, 43]])
b
b[:,-1] # acceso a la ultima columna
b[:2,:2] # acceso a la interseccion de las dos primeras columnas y las dos primeras filas
b[-2:, -2:] # acceso a la interseccion de las dos ultimas filas y dos ultimas columnas
b[1:3, : ]       # Acceso a los elementos de las filas 2 y 3 de todas las columnas<jupyter_output><empty_output><jupyter_text>Uso de máscaras para selección de partes de un array Otra forma de acceso a partes de un array de NumPy es mediante un array de booleanos que actúa como máscara.<jupyter_code>numbers = np.random.uniform(size=5)
numbers
# operaciones booleanas sobre todos los elementos de un array
# devuelve un array de booleanos de la misma dimension que el original
( numbers > 0.5 ) & ( numbers < 0.8 )      # selecciona algunos elementos de b
mascara = ( numbers > 0.5 ) & ( numbers < 0.8 )      
mascara
numbers[mascara] # equivalente a numbers[(numbers>0.5)&(numbers<0.8)]<jupyter_output><empty_output><jupyter_text>Métodos matemáticos y estadísticos El módulo __NumPy__ proporciona métodos que permiten realizar otras operaciones, como el mínimo elemento de un array, el máximo, la media de los elementos de un array, etc.  > Se puede encontrar la lista de funciones en [scipy.org](http://wiki.scipy.org/Numpy_Example_List) ><jupyter_code>mu = 0         # media
sigma = 0.1    # desviación
a = np.random.normal( mu, sigma, size = 10)    # distribución normal
a
print( a.sum() )
print( a.min() , "--" , a.argmin() )
print( a.max() , "--" , a.argmax() ) 
b
# suma de columnas para cada una de las filas con 'axis = 1'
print ( b.sum(axis = 1)  )   # suma por columnas<jupyter_output>[  6  46  86 126 166]<jupyter_text>__Media__<jupyter_code># media de todas las columnas
b.mean(axis = 0)
np.mean(b)<jupyter_output><empty_output><jupyter_text>__Varianza__<jupyter_code>np.var(b, axis = 0)<jupyter_output><empty_output><jupyter_text>__Desviación estándard__<jupyter_code>np.std(b, axis = 0)<jupyter_output><empty_output><jupyter_text>__Mediana__<jupyter_code>np.median(b)<jupyter_output><empty_output><jupyter_text>__Correlación__<jupyter_code>m = np.array([[10, 20, 30, 40],
              [12, 14, 16, 17]])
"""
Se calcula el coef. de correlación teniendo en cuenta que 
cada fila representa una variable
"""
np.corrcoef(m)      # correlación<jupyter_output><empty_output><jupyter_text>Ejercicios__* Dado el array `m1`, seleccionar los elementos de `m1` que son mayores que la media.__<jupyter_code>m1 = np.random.rand(10)
m1
# Solución
m1[(m1 > np.mean(m1))]<jupyter_output><empty_output><jupyter_text>* Dado el array `m2`, calcular la suma de todos los elementos de `m2`.<jupyter_code>m2 = np.array([[10, 20, 30, 40],
              [12, 14, 16, 17]])

# Solución
m2.sum()<jupyter_output><empty_output><jupyter_text>* Dado el array `m2`, calcular la suma de los elementos por columnas.<jupyter_code># Solución
m2.sum(axis = 0)<jupyter_output><empty_output>
{'fraction_non_alphanumeric': 0.09559149313247674, 'fraction_numerical': 0.02702702702702703, 'mean_word_length': 4.963672391017173, 'pattern_counts': {'":': 0, '<': 94, '<?xml version=': 0, '>': 101, 'https://': 0, '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': '23', 'dir': 'jupyter-structured-clean-dedup', 'id': '560241', 'license': 'MIT', 'n_tokens_mistral': 3395, 'n_tokens_neox': 3278, 'n_words': 1061, 'path': 'module2_Python/day2/numpy.ipynb', 'repo_name': 'albertopastormr/bod-ecv', 'size': '9028'}