miércoles, 23 de diciembre de 2020

Estadística descriptiva con Python

Debes estar muy aburrid@ en el trabajo, o no tienes nada mejor que hacer, para pinchar en un enlace con la palabra clave 'Estadística'. Pero... ya que has pinchado intentaré que se haga lo más ameno posible.

Esta entrada está pensada para hacer una pequeña visita guiada por la librería de pandas en su sección de estadística. Y es que cuando hablamos de Python y de estadística es inevitable que surja pandas, pues tiene debajo todo el poder de la librería numpy para hacer multitud de cálculos de una forma muy eficiente.

Ya verás qué pronto te encariñas con estos seres


Lo primero, (y evidentemente lo más importante) es importar dicha librería para poder utilizarla, para aquellas personas que no estéis familiarizadas se hace de la siguiente forma:


A continuación, vamos a 'cargar' algunos datos, para luego poder ver algunas de las estadísticas. ¿Que, qué datos voy a cargar? Pues las estadísticas de todos los Pokémon de la 1º a la 6º generación que son 800 en total (Para aquellas personas que no estén familiarizadas con este mundillo de Pokémon no os preocupéis, os vais a enterar igual).

¡Vamos a la obra!

En primer lugar, decirte que en pandas cuando hablamos de unos datos en formato 2D (con filas y columnas) se le denomina DataFrame (DF para acortar), y es muy habitual utilizar sus siglas para crear una variable que contenga un DataFrame. En este caso llamaré df_pkm al DataFrame con todos los datos de los Pokémon, y para leer esos datos usaremos .read_csv( ) y posteriormente echaremos un vistazo a los datos que tenemos con .head( )


Hagamos un inciso para que entiendas los datos.

Las columnas de Type hacen referencia a la 'familia' a la que pertenece uno de estos entrañables seres, por ejemplo, Pikachu (probablemente el más conocido) es de tipo eléctrico. Las columnas que van desde HP (Health Points) hasta Speed, hacen referencia a los atributos de estos seres, expresado en formato de puntos, en lo que a mayor número mejor es en este aspecto, y la columna Total es la suma de todas estas columnas. La columna Generation hace referencia a cuando salió dicho Pokémon en los juegos (del 1 al 6) y la Legendary hace referencia a si es un Pokémon con unas estadísticas muy especiales. Después de esta mini-chapa (pero fundamental para entender el contexto) sigamos con lo que cierne al título de esta entrada.

Este es Pikachu, seguro que te suena


Una vez tenemos cargado nuestro .csv en un DF, y antes de pasar a la parte de estadística, es fundamental comprobar el estado de nuestros datos, ya que, si no tenemos nuestros datos en un formato numérico (int, float, complex) no podremos operar con ellos como si fuesen números (Y sí, a veces ocurre que una columna que parece que está en formato numérico, pero en realidad está en un formato de texto (object)) . Para hacer una inspección rápida utilizaremos .dtypes 


Aquí podemos comprobar que todas nuestras columnas que tienen números están en un formato numérico, por lo tanto, ¡podemos continuar!

Ahora vamos a sacar estadísticas de todos estos datos. Para ello usaremos una de las herramientas que con una simple instrucción podemos sacar las estadísticas más importantes, y esa no es otra que .describe( )

Nada mal para una sola línea de código tan simple ¿eh?, este es el poder que reside en pandas. De esta forma de un simple vistazo tenemos muchas estadísticas a la vista, como puede ser la media, la desviación estándar, el mínimo o el máximo y los percentiles. Hay que decir, que la columna de Generation no tiene sentido en esta tabla, pues son valores discretos que van del 1 al 6 en función de cuando salió dicho Pokémon. Y como te puedes fijar solo ha hecho el análisis de aquellas columnas numéricas, el resto las ha obviado, por ello es fundamental comprobar primero el tipo de dato que tenemos.

Si quieres acceder a un dato en concreto solo tienes que seguir la siguiente sintaxis:


¿Y si en lugar de números lo vemos en un gráfico?

Otra ventaja que presenta pandas es que además de tener numpy debajo para hacer los cálculos numéricos, tiene también a matplotlib, una librería diseñada para representar gráficos. La conjunción de estas dos librerías más lo que aporta pandas hace que sea una de las herramientas más utilizadas en Python.

Volviendo a nuestros datos, probablemente la mejor forma de representar este tipos de datos sea mediante un diagrama de cajas y bigotes, para ello usamos .boxplot( ). Y seleccionaré aquellas columnas que realmente tienen interés, dejando de lado a 'Total' que es la suma de las que represento en el siguiente diagrama y a 'Generation':


Como puedes observar con comandos sencillos se pueden obtener buenos resultados.


¿Qué tal si ahora para finalizar ponemos fin a la lucha que ha habido siempre en el mundillo de Pokémon usando la estadística?

Por fin sabremos si es mejor el tipo agua, fuego o planta (Para aquellas personas que no estén al día, al principio de todos los juegos te hacen elegir un Pokémon de entre estos 3 tipos, pero en el juego en general hay muchos más tipos). Lo que haremos será hacer este mismo gráfico pero para cada uno de esos tipos (agua, fuego y planta) y compararemos las gráficas para salir de dudas de cuál es el mejor tipo.

En primer lugar, lo que haremos será filtrar nuestro DF dejando únicamente aquellos Pokémon de tipo agua, fuego o planta, y guardaremos ese nuevo DF en pkm_wfg (wfg de las siglas de water, fire y grass), para ello haremos uso .loc[ ] y de .isin( )

Y finalmente, usaremos de nuevo .boxplot( ) esta vez con unos parámetros adicionales, el más importante es el 'by' utilizado para agrupar, hay que tener en cuenta que hay que agregar la columna que queremos usar como agregación, por ese motivo, añadiremos 'Type 1' a las columnas que usaremos en nuestro diagrama de cajas y bigotes.


Queda claro que gana el tipo fuego, pero cuidadito con los outliers de tipo agua que pueden apagar muchos incendios, y el tipo planta se queda como robusto siempre entre los dos tipos. Conclusión, no hay conclusión, aún seguiremos debatiendo si elegir entre Squirtle, Charmander o Bulbasaur

Y bueno espero que con esta pequeña visita guiada a pandas te hayas hecho una idea de lo potente que es esta herramienta y que hay muchas aplicaciones a nivel estadístico. Espero que hayas llegado al final y que no te haya matado de aburrimiento.

¡Muchas gracias por leerme!

Todas las imágenes que no son capturas de pantalla han sido sacas de pixabay.com


jueves, 17 de diciembre de 2020

Diseño de un api ReST (III)

Continuamos con la serie de posts sobre apis ReST dedicándole una entrada a los parámetros en una petición (cero emoción).


Filtrado de recursos

Hemos visto que una petición GET a un servicio REST tendrá como respuesta un único recurso si se indica el identificador al final de la ruta o todos los contenidos en ella si termina en directorio. Se hace necesario un método por el cual los clientes puedan definir exactamente cuales son recursos que solicita.


Típica entrega de 500.000 recursos cuando no se han filtrado los resultados


HTTP cuenta con una herramienta para filtrar que recursos serán afectados por el método: la ‘query string’. Se trata de una sucesión de pares clave-valor con una sintaxis simple que se coloca a continuación de la ruta, después de una interrogación:

GET /ruta?Param1=valor1&param2=valor2&param3=valor3

El significado de esta petición es ‘los recursos contenidos en el directorio cuyas propiedades coincidan con los valores incluidos en la query’. Por ejemplo:

GET /clientes?estado=activo - Del directorio ‘clientes’ queremos solo aquellos cuyo estado sea ‘activo’

GET /facturas?fecha=17/7/2020 - Del directorio ‘facturas’ solo queremos las del 17 de julio.

DELETE /pedidos?estado=cancelado - Queremos que se eliminen solo los pedidos cancelados.

PUT /productos?estado=descatalogado - Queremos modificar los productos descatalogados 

Naturalmente habrá que programar la lógica de control necesaria en el lado del servidor para buscar estos parámetros en la petición y actuar en consecuencia cuando se encuentren. También será necesaria lógica para que cuando esos parámetros no estén presentes la petición sea rechazada (también se puede  devolver una cantidad limitada de recursos o implementar una paginación)

-Aquí está su respuesta, ¡con extra de JSON!


Seleccionando por criterios distintos a 'igual a'

Cuando se filtran resultados lo habitual es utilizar parámetros que coincidan con las propiedades de los recursos, pero no siempre es posible. Por ejemplo, si queremos entregar facturas entre dos fechas no encontramos una propiedad en los recursos ‘factura’ que nos sirva puesto que las facturas simplemente tienen ‘fecha’. Tenemos algunas opciones:

Utilizar parámetros creados específicamente para la búsqueda:

GET /facturas?fechaInicio=<fecha_inicio>&fechaFin=<fecha_fin>

Utilizar parámetros asociados con propiedades existentes, pero utilizando alguna sintaxis especial:

GET /facturas?fecha=gte:<fecha_inicio>,lte:<fecha_fin>

GET /facturas?fecha[gte]=<fecha_inicio>,fecha[lte]=<fecha_fin>

GET /facturas?fecha=gte:<fecha_inicio>,&fecha=lte:<fecha_fin>

Estos últimos tres ejemplos son igual de válidos, pero hay que tener en cuenta que la lógica para procesar esos parámetros en el servidor será tediosa de implementar (y fácil)


Otros usos

El uso principal de los parámetros es el de filtrar con más precisión los resultados que entregará el servidor al cliente pero podemos darle otros sin salirnos del camino que marca el protocolo HTTP. Recordemos que si lo abandonamos la cosa no será REST.

Estos usos exigen una implementación más compleja en el lado del servidor pero como se trata de cosas muy concretas e independientes del api en si se pueden generalizar e introducir en una librería reutilizable.


-Y ahora con un for revisas uno a uno los recursos a ver si cumplen el criterio de filtrado
-¿No lo hago mejor con el select?
-¿Se puede poner un for en el select?


Utilizando los parámetros para filtrar las propiedades de los recursos

Permite a los clientes indicar al servidor cuales propiedades de los recursos necesitan y minimizar así el trasiego de información y el consumo de memoria.

Tenemos libertad a la hora de definir el parámetro pero es habitual que tenga un nombre concreto que se puede utilizar en distintas peticiones y que sea de tipo múltiple. Por ejemplo

GET /libros?propiedades=id,titulo,autor

Con la adecuada implementación en el lado del servidor esta petición tendría como respuesta una lista de libros con los valores id, título y autor aunque el recurso tuviera muchos más valores.


Ordenando los resultados

Otro uso que puede darse a los parámetros es el de proporcionar a los clientes una manera de solicitar al servidor los recursos en un orden concreto. Debemos idear una sintaxis para el parámetro, existiendo muchas posibilidades. Los siguientes ejemplos son variaciones típicas y necesitan aproximadamente el mismo código en el servidor para extraer los valores:

GET /peliculas?orden=asc(titulo)

GET /peliculas?orden=desc(titulo)

GET /peliculas?orden=+titulo

GET /peliculas?orden=-titulo

También podríamos permitir varios campos para la ordenación. Quizás para esto sea más recomendable la segunda opción, con el más y el menos para poder alternan con más facilidad entre orden ascendente y descendente.

GET /productos?orden=+existencias,-precio


-Y aquí hemos puesto estas movidas para ordenar los resultados en el servidor
-¿Y cómo son esas movidas?
-Gordísimas

Y basta ya de parámetros. En el siguiente post continuaremos con más REST.