El objetivo de este primer capítulo es introducir algunos términos y conceptos generales de estadística, que nos serán de utilidad para discutir todos los tópicos de este curso y de posteriores cursos de este Programa de Actualización en Ciencia de Datos. La introducción es bastante general, por lo que a lo largo del texto se proveen de enlaces para seguir leyendo y profundizando en el tema.
La estadística es el estudio de la recolección, análisis, interpretación y organización de datos. El corolario de esta definición podría ser que además de ser una disciplina científica en si misma, la estadística es una disciplina auxiliar de todas las demás ciencias.
La estadística suele tener el mote de ser una disciplina obscura en el mejor de los casos y de una forma sofisticada de mentir en el peor de ellos. Sin embargo, en los últimos años ha comenzado a emerger una disciplina llamada ciencia de datos (data science en inglés), para muchos no es más que un nuevo y sexy nombre para llamar a la vieja estadística, algo así como una campaña mediática de mejoramiento de imagen. Para otros la ciencia de datos es un aporte valioso que si bien no es exactamente estadística, está contribuyendo a ésta con nuevos métodos, aproximaciones, preguntas, recursos ($$) y por supuesto mucha gente interesada en estos temas.
Una posible definición de la ciencia de datos y su relación con la estadística se muestra en el siguiente diagrama de Venn.
Según el diagrama, la estadística es una de las partes de la ciencia de datos. La gran diferencia entre la investigación tradicional y la ciencia de datos radica no en los conocimientos estadísticos (que ambas requieren) si no en las habilidades de hacking. En esto contexto hacking no hace referencia a la capacidad de vulnerar la seguridad de computadoras ajenas, si no a la capacidad técnica y creativa para encontrar soluciones mediante el uso de código.
Entonces, para empezar a hacer ciencia de datos necesitamos (además de un problema!):
En este curso aprenderemos ambas en paralelo. Esto puede sonar complicado, pero en realidad ambos aprendizajes se acompañan y apuntalan mutuamente. La mejor forma de probar que esto es cierto es hacer el curso, pero como adelanto podemos decir que esto funciona debido a que:
Aprender a programar sin tener un problema que resolver puede ser, para la mayoría de las personas, una tarea demasiado abstracta y poco placentera. El aprendizaje de la estadística nos proveerá de estos problemas.
La conceptos estadísticos tienen una fundamentación teórica que suele requerir de cierta formación matemática que no es común a la mayoría de los científicos. El manejo de un lenguaje de programación provee una ruta alternativa (o aún mejor complementaria), ya que nos permite comprender conceptos vía la simulación/experimentación.
Volviendo al diagrama de Venn, tanto la investigación tradicional como la ciencia de datos necesitan ir acompañadas de conocimiento sustantivo, esto hace referencia al conocimiento de una área particular del saber. La estadística puede ser de ayuda para estudiar genomas o partículas elementales, pero para poder hacer preguntas relevantes (y entender las respuestas) primero hay que comprender qué son los genomas y qué son las partículas elementales. La estadística NO es una máquina auto-mágica por donde entran datos en crudo por un lado y sale información por el otro (aunque a veces se publicite de esa forma). La estadística es una herramienta que nos ayuda a pensar y tomar decisiones de forma adecuada, pero requiere del conocimiento, el criterio y la responsabilidad de quien la usa.
El lenguaje de programación elegido para este curso es Python.
Python es:
Es posible usar Python para crear programas complejos que ocupan cientos o miles de lineas de texto. También es posible usar Python de forma interactiva. En este curso nos focalizaremos en esta segunda forma, para la cual las notebooks de Jupyter son ideales.
Para poder empezar a analizar datos lo que primero que hay que tener es ...datos! Estos datos provienen de experimentos, simulaciones, encuestas, observaciones, búsquedas en base de datos, etc. Rara vez los datos se nos presentan de forma inmaculada y lista para usar, por lo general hay que limpiarlos, procesarlos, combinarlos con datos de otras fuentes etc. Para sorpresa y frustración (sobre todo frustración) de quienes se inician en el análisis de datos esta etapa suele ser la que más tiempo involucra. En este curso veremos algunos rudimentos básicos de como hacer estas tareas.
Suponiendo que ya tenemos nuestros datos, lo recomendable es intentar ganar intuición sobre los datos que tenemos enfrente, tratar de ver qué dicen de nuestro problema, si es que dicen algo. Para ello se han desarrollado una colección de métricas y métodos colectivamente llamados Análisis exploratorio de datos (EDA por sus siglas en inglés), la cual se compone básicamente de dos herramientas complementarias:
La primera se ocupa de describir de forma cuantitativa un conjunto de datos. Para ello se recurre a un conjunto de medidas que nos ayudan a resumir los datos en unos pocos números. En general se habla de medidas de centralidad, de dispersión. La estadística descriptiva no intenta establecer conclusiones más allá de los datos disponibles, solo se limita a describir la muestra (o dataset) sin intentar decir nada de la población de la cual provienen esos datos, es decir no hace ni generalizaciones ni inferencias.
La segunda se encarga de mostrar los datos de forma que sea más fácil interpretarlos. Los seres humanos tenemos un sistema visual muy poderoso y la visualización de datos intenta aprovechar sus virtudes a la vez que mitigar sus defectos.
Tanto en análisis de datos como en programación se habla de variables, aunque el significado no es exactamente el mismo, veamos:
En estadística una variable es simplemente una cantidad que puede tomar un valor a partir de un conjunto de valores permitidos. Las variables se suelen clasificar de la siguiente manera (aunque hay otras clasificaciones):
Métricas o cuantitativas: Son variables con las cuales es posible establecer un orden y computar distancias. Cuando en la escala existe un cero es posible además calcular proporciones, por ejemplo 1 hora es la mitad de 2 horas (por que 0 horas implica la ausencia de tiempo transcurrido). En cambio $40 ^\circ \text{C}$ no es el doble de $20 ^\circ \text{C}$, por que el cero de la escala Celcius es un punto totalmente arbitrario (contrario al cero de la escala Kelvin).
Cualitativas: Son el tipo de variables que indican cualidades, o atributos.
En Python, como en otros lenguajes de programación, se le llama variable a un espacio en la memoria de la computadora que almacena un valor determinado y que tiene asociado un identificador, es decir un rótulo o nombre. Existen distintos "tipos" de variable, en Python los tipos más comunes que encontraremos serán:
Para saber el tipo de una variable en Python podemos usar la función type()
:
In [1]:
type(42)
Out[1]:
In [2]:
type(42.0)
Out[2]:
In [3]:
type("42")
Out[3]:
In [4]:
type(True)
Out[4]:
Existen muchos otros "tipos" de variables en Python, incluso los usuarios avanzados pueden crear sus propios tipos de variables! Por ahora estos tipos son suficientes. Tener variables es útil por que con ellas podemos hacer operaciones, por ejemplo operaciones matemáticas.
In [5]:
1 + 1 # esto devuelve un entero
Out[5]:
In [6]:
1. + 1 # esto devuelve un float
Out[6]:
In [7]:
2 / 1 # en Python3 la división siempre devuelve floats
Out[7]:
In [8]:
"42" + 1 # esta operación no tiene sentido y por lo tanto Python devuelve un error!
In [9]:
'4' * 4 # esta operación SI tiene sentido en Python, es lo que esperabas?
Out[9]:
In [10]:
1 > 2 # acá comparamos variables y obtenemos un booleano
Out[10]:
In [11]:
2 == 2 # ojo que el operador "igualdad" es "==" y no "="
Out[11]:
En los ejemplos anteriores hemos realizado algunas operaciones con variables, pero no las hemos guardado en ningún lado. Puede no ser obvio al principio pero para poder hacer tareas relativamente complejas es necesario poder guardar variables y darles nombres (o identificador). De esa forma podemos realizar una operación, como por ejemplo 2 + 2
y guardar el resultado bajo algún nombre para luego usarlo cuando lo necesitemos.
Los nombres de las variables en Python siguen ciertas reglas que es necesario respetar.
_
a-z
, A-Z
0-9
(solo que no como primer carácter). Python 3 permite además usar otros caracteres, como por ejemplo letras griegas, la ñ
, caracteres acentuados, etc; aunque el uso de este tipo de caracteres para nombrar variables no esta muy difundido aún.
Por convención, los nombres de las variables comienzan con una letra minúscula al igual que los nombres de las funciones (ver más adelante). Los nombres que comienzan con mayúsculas están reservados para las clases, en este curso no vamos a estudiar clases.
Para asignarle un valor a una variable se usa el símbolo =
In [12]:
a = 2 + 3
En la celda anterior estamos diciendole a Python: "Tome el valor 2
súmele el valor 3
y guárdelo en la variable que se llama a
".
Esto lo podemos comprobar al ejecutar:
In [13]:
a
Out[13]:
No solo es posible usar variables para guardar valores, también podemos operar con ellas. Por ejemplo podemos preguntar si nuestra variable es mayor que cierto valor.
In [14]:
a > 2
Out[14]:
O podemos restarle un valor
In [15]:
a - 1
Out[15]:
Incluso podemos actualizar el valor de las variables, por ej.
In [16]:
a = a + 1
a
Out[16]:
El ejemplo anterior muestra que el signo =
no es el operador igualdad (que como ya vimos es ==
). No es correcto decir que a
es igual a la expresión a + 1
. Aunque si nos expresamos de esa forma lo más probable es que nos entiendan y nadie nos diga nada.
Si tuvieramos que leer la celda anterior en voz alta diríamos: "tome el valor de la variable a
, súmele 1
y guarde el resultado en la variable a
".
Si tratamos de usar una variable que no ha sido definida previamente obtendremos un mensaje de error:
In [17]:
z
Los errores son parte central de la programación y hay que acostumbrarse a cometerlos ya que es así como se avanza en la escritura de un programa. Al producirse errores, Python entrega mensajes que son muy informativos y por lo tanto útiles para solucionar el error, por lo que es muy beneficioso aprender a interpretarlos y prestarles mucha atención cuando ocurren.
El proceso de corrección de errores de un programa se llama debugging y es quizá una de las tareas más demandantes al escribir código. Python fue pensado como un lenguaje fácil de leer debido a que en general uno pasa más tiempo leyendo código (para arreglar los errores) que escribiéndolo. Mitad broma, mitad en serio se dice que si el debugging es el proceso por el cual se eliminan errores la programación debe ser el proceso por el cual se introducen los errores.
Nota: en muchos lenguajes de programación (como C/C++ o Fortran) antes de poder asignar valores a variables es necesario declararlas. Declararlas, quiere decir que tenemos que indicar que nuestro programa usará una variable de nombre tal que será del tipo cual. Recién una vez declarada la variable podemos asignarle valores concretos. En Python esto no es necesario, además en Python está permitido cambiar el tipo de una variable, por ejemplo podemos usar la variable a
que hacía referencia a enteros int
para referirnos a un string
. El nombre técnico de esto es el de tipado dinámico (el tipo de una variable no es fijo, si no dinámico).
In [18]:
a = '42'
a
Out[18]:
Puede parecer poco lo que hemos aprendido hasta ahora, pero ya sabemos suficiente Python como para hacer algunos cálculos. Nada mejor para aprender a programar que intentar solucionar problemas. Un problema que podemos resolver con lo que sabemos de Python es calcular el valor de la media de un conjunto de valores.
Una forma fácil de perder tiempo al intentar resolver problemas es no tener demasiado claro cual es el problema que se intenta solucionar, asi que antes de calcular la media veamos primero como se define.
Uno de los cómputos más elementales en estadística consiste en calcular la media, también conocido como promedio o valor esperado. Existen varias expresiones matemáticas que nos permiten calcular la media, quizá la más común sea:
$$E[\mathbf{x}] = \frac{1}{n} \sum_{i=1}^n{x_i}$$Es decir, para obtener la media de $n$ valores, los sumamos y luego dividimos en $n$. En estadística se suele usar la notación $E[\mathbf{x}]$ nomo una forma abreviada de referirse a la media (o valor esperado) de $\mathbf{x}$.
Es común en estadística distinguir entre la media de una muestra, es decir de un conjunto finito de datos (que se suele simbolizar con el símbolo ${}^\bar{}$ por ejemplo $\bar x$) y la media de la población (que se suele simbolizar con la letra griega $\mu$), en la mayoría de los casos la población es un objeto imaginario al que no tenemos acceso y que solo aproximamos, en la gran mayoría de los casos $\bar x$ es un buen estimador de $\mu$, por ejemplo a medida que juntemos más y más datos el valor de $\bar x$ se aproximará al de $\mu$.
Dado un conjunto de valores $\mathcal{D} = \{1, 2, 3, 4, 5, 6\}$, una forma de calcular la media, usando Python, es:
In [19]:
media = (1 + 2 + 3 + 4 + 5 + 6) / 6
media
Out[19]:
El código que acabamos de escribir no difiere mucho de lo que podríamos haber realizado con una calculadora, primero tenemos que ingresar los numeros a mano y además tenemos que saber exactamente la cantidad de números que estamos sumando para luego saber por que cantidad dividir. Usando un lenguaje de programación podemos hacer algo bastante más cómodo. Pero para ello tenemos que aprender un par de conceptos nuevos.
In [20]:
lista = [] # crea una lista vacia
lista
Out[20]:
In [21]:
num = [1, 2, 3, 4, 5, 6]
num, type(num)
Out[21]:
Las listas son un tipo particular de lo que se conoce genéricamente como estructuras de datos, es decir una forma particular de organizar y almacenar datos de forma que sea conveniente trabajar con esos datos. Las listas de Python son convenientes por que permiten no solo almacenar valores sino realizar muchas otras operaciones de forma sencilla. Una operación común es preguntarle a una lista cual es su longitud, es decir cuantos elementos contiene. Esto se hace con la función len()
.
In [22]:
len(lista)
Out[22]:
In [23]:
len(num)
Out[23]:
Otra operación conveniente es la de sumar todos los valores de una lista, esto lo hacemos usando la función sum()
.
In [24]:
sum(num)
Out[24]:
Con estas dos nuevas funciones que hemos aprendido podemos re-escribir el cálculo de una media de la siguiente forma:
In [25]:
media = sum(num) / len(num)
media
Out[25]:
Esta nueva forma de calcular la media tiene varias ventajas. Una de ellas es que resulta más automática que la anterior, cada vez que cambiemos los valores contenidos en la lista num
podremos ejecutar la celda anterior y obtener el valor de la media. Este es un buen momento para probar que esto es cierto.
El código de la celda anterior es mucho más general que nuestra primer versión del cómputo de la media. Pero tiene un inconveniente, veamos. Es común al escribir código que necesitemos repetir una operación muchas veces, una solución simple sería copiar y pegar el código cada vez que necesitemos calcular una media. El problema con esta aproximación es que es tediosa y muy propensa a cometer errores, pensemos que operaciones más complejas podrían requerir de cientos de líneas de código, no una sola como el ejemplo de la media. Una forma de resolver nuestro problema sería crear una función que calcule la media, de tal forma que podamos llamar a esa función cada vez que la necesitemos. En Python crear funciones es muy simple, veamos:
In [26]:
def calcular_media(a):
res = sum(a) / len(a)
return res
def
. Le llamamos reservada ya que tiene significado especial para Python y no deberíamos usar ese nombre para otra cosa que no sea definir funciones. def
, y en la misma linea, va el nombre de la función, que puede ser el que más nos guste (siempre que respetemos las reglas para nombrar funciones, que son las mismas que para nombrar variables y que ya vimos más arriba).a
.:
.return
seguida del valor que devuelve la función, es decir del resultado de la función. Esta ultima línea es opcional ya que es posible definir funciones que no devuelvan valor alguno (ya veremos ejemplos).Es importante notar un detalle, muy particular de Python, todo el contenido de la función (lineas 2 y 3) está escrito usando una sangría de 4 espacios, la sangría es obligatoria y le indica a Python cual es el cuerpo de la función. Los 4 espacios son solo una convención, podrían ser 2, 3, 8, etc, lo importante es respetar la misma cantidad.
Una vez definida una función se la puede llamar usando su nombre y pasándole los argumentos necesarios.
In [27]:
calcular_media(num)
Out[27]:
Algo para prestar atención, el nombre de la variable que le pasamos a nuestra función no tiene nada que ver con el nombre del argumento de la función. Es decir nosotros le pasamos a la función un objeto llamado num
, internamente calcular_media
le asigna a ese objeto el alias a
. Técnicamente decimos que a
es una variable local de la función calcular_media
, es decir a
solo existe dentro de esta función, fuera de ella a
podría no existir o ser una variable completamente distinta.
In [28]:
calcular_media([1, 3, 4]), a # dentro de la función `a` contiene una lista, fuera el valor 6
Out[28]:
Otra convención en Python es escribir las funciones incluyendo un docstring
. Que no es más que un string
que se escribe a partir de la segunda línea de la función y que Python ignora por completo al ejecutar la función. La información contenida en el docstring
no es para Python, es para los humanos que lo usan! El docstring
consiste en una explicación sobre las operaciones que realiza una función, qué valores espera la función como entrada y qué valores devuelve (si es que devuelve algo). Los docstring se escriben usando comillas triples """
esto permite que el docstring tenga varias lineas. El estilo exacto de los docstring varía, pero la mayoría de las funciones en Python tratan de mantener un mismo estilo para sus docstrings. Un ejemplo de docstring sería:
In [29]:
def calcular_media(a):
"""
Calcula la media partir de una lista.
Parametros
----------
a : lista
Contiene los valores a promediar
Resultado
----------
res : float
La media de los valores contenidos en `a`
"""
res = sum(a) / len(a)
return res
Entonces, la forma general de un docstring sería:
In [30]:
"""
Descripción, ¿para qué sirve la función? ¿qué tarea realiza?
Parametros
----------
nombre : tipo
descripción
Resultado
----------
nombre : tipo
descripción
""";
El docstring no solo puede ser leído directamente del código, si no que puede ser usado por Python y por varias herramientas externas. Por ejemplo la función help()
de Python ofrece ayuda sobre una función al usuario "mostrando el docstring". Por ejemplo, si quisieramos ver qué hace la función len()
In [31]:
help(len)
Esto no solo sirve para funciones que ya vienen con Python, si no que el mismo mecanismo es usado para funciones definidas por los usuarios.
In [32]:
help(calcular_media)
Jupyter (lo que estamos usando para mostrar este documento y ejecutar el código), también permite acceder al docstring usando shift + TAB
In [33]:
calcular_media # seleccioná esta celda y presioná shift + tab
Out[33]:
Dentro de Jupyter también se puede acceder a la ayuda escribiendo ?
o ??
luego de la función y presionado enter
.
Todo muy bien hasta ahora, pero ¿Qué pasaría si inadvertidamente le pasáramos una lista vacía a nuestra función?
In [34]:
calcular_media([])
Obtenemos un error! Antes de seguir leyendo intentemos entender cual es la causa del error y en que parte del código se encuentra.
Como se puede ver el problema se debe a que la división por 0 no está definida. Este es un tipo de error que no se debe a que metimos la pata al programar el cálculo de la media, se debe a una imposibilidad matemática. Es, si lo pensamos, un error esperado algo que es razonable que ocurra, en la jerga pitónica decimos que es una excepción. Python permite lidiar con las excepciones de forma elegante (en vez de dejar que el programa falle horriblemente). Veamos primero el código y luego la explicación.
In [35]:
def calcular_media2(a):
"""
Calcula la media partir de una lista.
Parametros
----------
a : lista
lista que contiene los valores a promediar
Resultado
----------
res : float
La media de los valores contenidos en `a`
Devuelve una advertencia si `a` está vacía.
"""
try:
res = sum(a) / len(a)
return res
except ZeroDivisionError:
print('la lista está vacía, por favor use una lista con números')
In [36]:
calcular_media2([])
Ahora la función en vez de fallar devuelve un mensaje.
La novedad es que usamos el bloque try-except
. Lo que hace esto es intentar correr lo que está dentro de cuerpo de try
, si llegara a ocurrir un error, que en este caso hemos especificado que sea del tipo ZeroDivisionError
, entonces se ejecuta lo que sea que esté dentro del bloque except
en este caso un mensaje, pero podría ser otra cualquier cosa. Si hubieramos escrito solo except:
(sin especificar ZeroDivisionError
) entonces el bloque except
se ejecutaría sin importar el tipo de error, esto si bien es legal no se recomienda ya que puede llegar a ocultar bugs. Por ejemplo sum()
, y tambiénlen()
, devuelven un error si lo usáramos con un entero en vez de una lista.
In [37]:
calcular_media2(1)
La media es una buena descripción si los datos que estamos midiendo son más o menos similares, pero puede ofrecer una visión muy distorsionada si los datos no son muy similares entre si, por ejemplo como puede suceder con los ingresos, algunas personas apenas ganan unos pocos pesos mientras que otras acumulan millones por mes.
La mediana es el número que separa un conjunto de datos en una mitad superior y otra inferior. La mediana es una medida más robusta que la media a valores extremos. Veamos un ejemplo para el conjunto $\mathcal{D} = \{1,2,3,4,5\}$, la media y la mediana es igual a 3. Si ahora agregaramos a este conjunto un nuevo valor, por ejemplo 100. La media pasará a ser $\eqsim 19,2$ mientras que la mediana apenas cambiará a 3.5 $\left ( \frac{3 + 4}{2} \right )$.
Para calcular la mediana necesitamos algunos conceptos nuevos. Una característica de las listas de Python es que es posible acceder a los elementos contenidos en ellas mediante índices. Los índices deben ser enteros, empiezan en 0 y terminan en len(.)-1
.
In [38]:
lista = [5, 4, 3, 2, 1]
lista[0] # el cero-ésimo elemento de la lista
Out[38]:
In [39]:
lista[2]
Out[39]:
In [40]:
lista[5] # este índice no existe en este caso y Python nos lo indica con un error
Los índices puede ser negativos
In [41]:
lista[-1] # devuelve el último elemento
Out[41]:
No solo es posible acceder a elementos individuales de una lista, también se puede acceder a rebanadas (slices
).
In [42]:
lista[1:] # del elemento 1 al final
Out[42]:
In [43]:
lista[1:4] # del elemento 1 al 4
Out[43]:
In [44]:
lista[::2] # del primer elemento al último "de a 2"
Out[44]:
In [45]:
lista[::-1] # del primer elemento al último "de a -1", invierte el orden!
Out[45]:
In [46]:
lista[:] # un caso trivial, del primer al último elemento (equivale a no usar un slice!)
Out[46]:
Ya estamos un poco más cerca de calcular la mediana, pero nos falta aprender lo que se conoce como control de flujo. Es común que al escribir un programa necesitemos ejecutar una acción de forma condicional, algo del estilo si pasa A entonces X y si no B entonces Y. Esto se consigue en Python con el bloque if-else
.
In [47]:
if 2 > 1: # esto es cierto
print('hola')
else:
print('chau')
Si la condición a la derecha de if
evalua como verdadero se ejecuta el cuerpo dentro de if
, de lo contrario se ejecuta el bloque else
. Veamos el siguiente ejemplo:
In [48]:
if 1 > 2: # esto es falso
print('hola')
else:
print('chau')
El bloque else
es opcional
In [49]:
if True: # esto es siempre cierto
print('hola')
Es posible revisar más de una condición, en el siguiente ejemplo debido a que la expresión a la derecha de if
evalua como falso se evalua la siguiente condición, que en este caso evalua como verdadera.
In [50]:
if 1 > 2: # esto es falso
print('hola')
elif 2 > 1: # esto es cierto, solo se ejecuta si if evalua falso
print('hello')
else:
print('chau')
El bloque if-else
es algo similar al bloque try-except
, pero este segundo bloque está restringido a manejar posibles errores, mientras que el bloque if-else
sirve para tomar decisiones que no tiene que ver con errores (o excepciones) si no con el funcionamiento normal de un programa.
Las listas vacías y otros contenedores vacíos (que veremos luego), el número 0, evalúan como False
. Listas (u otros contenedores) con elementos, y números distintos de 0 evalúan como True
.
In [51]:
if []: # esto es falso
print('hola')
Todavía faltan un par de detalles, pero los vamos a ver directamente en la función. Es común al aprender una lenguaje encontrarse con código que contiene elementos desconocidos o "frases" que no sabíamos que eran posibles o legales.
In [52]:
def mediana(lista):
"""
Ejercicio escribir docstring!
"""
lista_ordenada = sorted(lista)
lista_len = len(lista_ordenada)
idx = int((lista_len - 1) / 2)
if lista_len % 2:
return lista_ordenada[idx]
else:
return (lista_ordenada[idx] + lista_ordenada[idx + 1]) / 2
Esta es una función bastante más compleja que calcular_media()
. Veamos línea por línea qué es lo que hace.
lista
.int
para asegurarnos que el resultado es un entero.True
solo si lista_len
es impar ¿Podés darte cuenta por qué? (tip: probá ese bloque de código en una celda separada).lista_len
es par entonces se ejecutará el bloque else
. La razón de tener este bloque es que si la cantidad de elementos es par NO es posible obtener el valor "del medio", entonces la mediana la computamos como un promedio de los dos valores "del medio".
In [53]:
mediana([1, 2, 3, 4, 5])
Out[53]:
Además de la media y la mediana existen otras medidas de centralidad como la moda. La moda es simplemente el valor más frecuente en un conjunto de datos.
Mide la dispersión de un conjunto de valores. Es cero para un conjunto de valores idénticos.
$$V(\mathbf{x}) = E[(\mathbf{x} - \mu)^2] = \frac{1}{n} \sum_{i=1}^n (\mu - x_i)^2 \tag{1}$$Donde $\mu$ es la media de $\mathbf{x}$,
Al igual que pasa con la media, se suele distinguir entre la varianza de la población $\sigma^2$ y la varianza de una muestra $s^2$. Pero a diferencia de la media se puede probar que la fórmula anterior para $s^2$ subestima el valor de $\sigma^2$. Es posible obtener un mejor estimador con una ligera modificación de la ecuación 1, la cual consiste en dividir por $n-1$ en vez de $n$. En la práctica esta corrección solo es importante para cuando se tienen pocos datos, a medida que los datos aumentan no hay mucha diferencia entre usar una u otra fórmula. Esto es facil de ver intuitivamente, para $n=2$ la diferencia entre $n$ y $n-1$ es relativamente grande, mientras que para $n=100$ esta diferencia es mucho más pequeña.
A partir de la definición de varianza, vemos que para calcularla debemos repetir la operación $(\mu - x_i)^2$ para cada valor en $\mathbf{x}$ es decir debemos iterar, esto es una operación muy común en programación. Una de las formas más comunes de iteración es mediante el bucle for
, por ejemplo para iterar sobre todos los valores contenidos en la lista num
hacemos:
In [54]:
for i in num:
print(i)
Una operación muy común es querer iterar sobre una lista de valores, modificarlos y guardarlos en otra lista, por ejemplo:
In [55]:
num_2 = []
for i in num:
num_2.append(i**2)
#print(num_2)
num_2
Out[55]:
num
, en cada iteración i
toma un valor distinto.i
y adjuntamos ese valor a la lista num_2
.Es importante notar que append agrega elementos al final de la lista, por eso el cuadrado de 6 ( el último elemento de num
) es el último elemento de num_2
. Para ver append en acción descomentá la linea 4 de la celda anterior.
Ahora si ya estamos en condiciones de calcular la varianza.
In [56]:
def varianza(valores):
"""
ejercicio escribir docstring!
"""
media = calcular_media2(valores)
var = []
for i in valores:
var.append((media - i) ** 2)
return calcular_media2(var)
In [57]:
varianza([0, 1, 2.72, 3.14])
Out[57]:
¿Notaron que la función varianza
usa la función calcular_media2
? No hay ningún problema en que una función llame a otra función (incluso existen funciones que se llaman a si mismas!). Esto permite escribir funciones complejas a partir de otras funciones más simples.
No solo es posible iterar sobre listas, se puede iterar sobre otros objetos, la única condición es que sean iterables. Otros objetos iterables son las cadenas. Por ejemplo:
In [58]:
for c in 'hola':
print(c)
El siguiente patrón, donde iteramos a la largo de una lista de enteros es tan común en Python (y otros lenguajes) que existe varias funciones que facilitan esta tarea. Una de ellas es range
:
In [59]:
d = []
for i in [0, 1, 2, 3, 4, 5, 6, 7, 8]:
d.append(i ** 2)
d
Out[59]:
range
usa la sintaxis [start,stop,step] a fin de generar enteros desde start
, hasta stop
(sin incluirlo) y opcionalente de a step
pasos (por defecto 1). start
es también opcional en cuyo caso empezará en 0. Como pueden ver la sintaxis es similar a lo que ya vimos con la rebanadas de una lista. La diferencia es que las rebanadas operan sobre una lista existente y la función de range
es la de generar un objeto que contiene enteros.
In [60]:
d = []
for i in range(0, 9):
d.append(i ** 2)
d
Out[60]:
En Python3 range
es un objeto que contiene las reglas para devolver valores, pero no los valores en si. Esto es un truco que permite ocupar menos memoria. Intuitivamente se puede ver que se requiere menos memoria para especificar la regla, "devuelva todos los enteros de 0 a 1 millón", que para escribir un millón de enteros.
En el siguiente ejemplo vemos una diferencia entre el objeto range
y una lista generada a partir de convertir range
usando el comando list
.
In [61]:
range(9), list(range(9))
Out[61]:
Como veníamos diciendo al trabajar con Python es común repetir una frase como la siguiente:
Este patrón es tan común que Python ofrece una versión alternativa, la cual es considerada por la mayoría de Pitonistas como más simple y clara, aunque al principio puede que no se vea como simple y clara :-(
Esta versión alternativa se llama list comprehension, o comprensión por listas. Y luce de la siguiente forma:
In [62]:
d = [i ** 2 for i in range(9)]
d
Out[62]:
En palabras podríamos leerla como, "genere una lista tomando la variable i
elevándola al cuadrado y repitiendo esto para todos los valores de i
en el rango de 0 a 9".
Usando list compreherions podemos calcular la varianza de la siguiente forma:
In [63]:
def varianza(valores):
"""
ejercicio escribir docstring!
"""
media = calcular_media2(valores)
var = [(media - i) ** 2 for i in valores]
return calcular_media2(var)
La desviación estándar es la raíz cuadrada de la varianza, en muchos problemas teóricos resulta más fácil manipular varianzas que desviaciones estándar, pero en general resulta más simple interpretar las desviaciones estándar ya que las unidades son las mismas que las de los datos.
$$\sigma = \sqrt{V(x)}$$Los diccionarios son similares a las listas, una de las diferencias es que para indexar una lista solo es posible usar enteros, en cambio para indexar un diccionario podemos usar otros tipos.
Un diccionario es una forma de mapear un conjuntos de índices (llamados claves o keys) y un conjunto de valores. A cada par clave-valor (key-value) se le suele llamar item.
Podemos crear un diccionario vacio de la siguiente forma:
In [64]:
diccionario = {} # o también diccionario = dict()
type(diccionario)
Out[64]:
Y luego podemos agregar elementos especificando la clave entre []
y asignando un valor mediante el signo =
In [65]:
diccionario['Santiago'] = 'Maldonado'
diccionario
Out[65]:
Al igual que con las listas, no es necesario crear un diccionario vacío y luego agregar valores, podríamos haber creado el diccionario de forma directa. En el siguiente ejemplo tenemos los nombres de 5 ciudades y sus poblaciones.
In [66]:
pob = {'BSAS':15594428,
'Córdoba':3304825,
'Santa Fe':3300736,
'CABA':2891082,
'Mendoza':1741610}
Si ahora quisiramos saber la población de cordoba, bastaría con escribir:
In [67]:
pob['Córdoba']
Out[67]:
Una operación que suele ser útil es preguntar si una clave existe en un diccionario, esto se logra con el operador in
:
In [68]:
'San Luis' in pob
Out[68]:
El operador in
funciona tanto con listas como con diccionarios, aunque internamente no funciona de igual forma para ambas estructuras de datos. En el caso de las listas el tiempo de demora de esta operación es proporcional a la longitud de la lista, mientras más elementos estén contenidos en la lista más tardaremos en obtener una respuesta. Por el contrario el tiempo de demora es casi constante para los diccionarios. Quienes tengan mayor curiosidad sobre como se logra esta característica pueden leer sobre Hash table.
Si quisieramos saber si un valor
está contenido en un diccionario deberíamos hacer:
In [69]:
3304825 in pob.values()
Out[69]:
Así como iteramos sobre los valores contenidos en un lista es posible iterar sobre los valores contenidos en un diccionario:
In [70]:
for k, v in pob.items():
print(k, v)
Es común usar estadísticos como la media y la desviación estándar para resumir (o comprimir) un conjunto de datos. Esto proceso de compresión de información puede conducir a la pérdida de información útil, por lo que suele ser buena idea usar otras formas de representar datos, una muy común es mediante histogramas. Un histograma es una representación visual que muestra el número de veces que aparece un número es un conjunto de datos (frecuencia). En el próximo capítulo veremos como hacer este tipo de gráficos. Pero antes de llegar a graficar veamos como podemos usar un diccionario para calcular estas frecuencias. Supongamos que tenemos una lista de valores como la siguiente
In [71]:
lista = [3, 5, 5, 2, 2, 4, 1]
Y queremos saber cuantas veces aparece cada número en esa lista, el 1 una vez, el 2 dos veces el 3 una vez, etc. Usando un diccionario podemos realizar esta tarea de forma eficiente y relativamente sencilla.
In [72]:
def frecuencias(datos):
"""
"""
frec = {}
for c in datos:
if c not in frec:
frec[c] = 1
else:
frec[c] += 1
return frec
In [73]:
lista = [1, 2, 2, 3, 4, 5]
frecuencias(lista)
Out[73]:
In [74]:
unatupla = (42, 'a', [3])
unatupla
Out[74]:
Podemos indexarlas
In [75]:
unatupla[1]
Out[75]:
Y podemos tomar rebanadas
In [76]:
unatupla[1:]
Out[76]:
Una diferencia con las listas es que las tuplas son inmutables, es decir no pueden ser modificadas una vez creadas
In [77]:
unatupla[0] = 0
Dado que las tuplas y las listas parecidas es común que surga la pregunta ¿Cúando es conveniente usar una y cuando la otra?
Al dar los primeros pasos con Python esta elección no es demasiado relevante, ya que en muchos casos el uso es más o menos indistinto. De todas formas es bueno saber que uno de los criterios usados para elegir entre listas y tuplas tiene que ver con el tipo de elementos que vamos a almacenar. Si bien tanto las tuplas como las listas pueden contener elementos de distinto tipo (por ej strings
e int
). Es más común que se use listas para almacenar elementos del mismo tipo y tuplas para elementos de distinto tipo.
In [78]:
s = 'ciencia'
In [79]:
s[0]
Out[79]:
In [80]:
s[::-1]
Out[80]:
También es posible realziar otro tipo de operaciones que normalmente consideramos definidaas solo para números, es posible sumar strings
In [81]:
s + ' de ' + 'datos'
Out[81]:
y multiplicarlos
In [82]:
(s + ' ') * 5
Out[82]:
Python ofrece muchos métodos para operar sobre strings
.
In [83]:
s.capitalize()
Out[83]:
In [84]:
s.count('c')
Out[84]:
Para escribir un string podemos usar comillas simples o dobles, cual usar es una cuestión de preferencia.
In [85]:
'simples'
Out[85]:
In [86]:
"dobles"
Out[86]:
También es posible usar comillas triples (triples-simples o triples-dobles)
In [87]:
'''tri-
ples'''
Out[87]:
La ventaja de las comilas triples es que permite escribir un string que ocupe varias lineas (como ya vimos con los docstrings). \n
se usa para indicar que el string
contiene una nueva linea de texto
In [88]:
print('''tri-
ples''')
In [89]:
print('tri-\nples')
En los ejemplos anteriores hemos visto como calcular la media, mediana y la varianza. Vimos además que es posible encapsular código dentro de funciones y así reutilizarlo. El principal motivo de estos ejemplos fue motivar el aprendizaje de Python. En la práctica el cálculo de las funciones como la media o la varianza son tan comunes que resulta muy conveniente poder acceder a estas funciones sin necesidad de que debamos escribirlas nosotros. Es por esto que en Python (y otros lenguajes) existen las bibliotecas, que permiten extender el lenguaje. En el fondo estás bibliotecas (llamadas también librerías, por una transliteración del ingles library) no son otra cosa que un conjunto de funciones del estilo que aprendimos a escribir en este capítulo. En general las bibliotecas contienen funciones optimizadas, es decir funciones que corren rápido o que reducen la posibilidad de errores numéricos. Por lo que la ganancia al usarlas no es solo que nos ahorran tiempo si no que en general nos ahorran dolores de cabeza!
Python posee un extenso ecosistema de bibliotecas, muchas de ellas específicas para hacer ciencia. Algunas de esas bibliotecas más usadas son:
Incluso algunas bibliotecas se construyen sobre otras bibliotecas. En el siguiente esquema se ve parte del ecosistema de Python ordenado (radialmente). Donde un nivel se apoya sobre los inferiores como en un cebolla.
Además de estas bibliotecas externas Python se distribuye con algunas bibliotecas estándar, por ejemplo math
tiene varias funciones matemáticas. Para poder usarla necesitamos importarla de la siguiente forma.
In [90]:
import math
Ahora que la importamos podemos usarla por ejemplo para calcular un logaritmo.
In [91]:
math.log(10)
Out[91]:
Qué otras funciones están disponibles dentro de math
? Una forma de averiguarlo es usando la ayuda que nos ofrece jupyter. Recordá que basta escribir math.
seguido de Tab
para que Jupyter nos haga recomendaciones!
Existen varias formas de importar modulos en Python, una alternativa a lo ya visto es importar una función específica, por ejemplo sqrt
.
In [92]:
from math import sqrt
In [93]:
sqrt(4)
Out[93]:
También podemos importar una función pero cambiandole el nombre
In [94]:
from math import sqrt as raiz
In [95]:
raiz(4)
Out[95]:
?
o ??
.¿Cual es la diferencia entre usar una u otra forma?Ejecutar en una celda:
import this
Este texto es conocido como el Zen de Python. Es común que al escribir código pensemos en más de una forma de escribir lo mismo. Dentro de la comunidad de Python se suele dar preferencia a una de estas formas sobre las otras, debido a que es más simple de comprender, menos propensa a errores, más eficiente, más comunmente usada, etc. Un código que satisface estos criterios, mejor que otro, se dice que es Pytónico.
frecuencias
te puede servir.Python es un lenguaje muy rico y expresivo que cuenta con muchas otras características útiles las cuales hemos omitido ya que no serán necesarias para el resto del curso o que podrán ser introducidas a medida que sea necesario.
A continuación listamos algunas de estas características:
Hay mucho material disponible para quienes tengan interés en aprender estos tópicos, nosotros proponemos los siguientes libros, en orden de complejidad/profundidad