Pandas Profiling es una librería que genera informes desde un DataFrame de pandas. La función pandas df.describe()
que usamos normalmente en Pandas es genial pero es un poco básica para un análisis exploratorio de datos más serio y detallado. pandas_profiling
extiende el pandas DataFrame con df.profile_report()
para un análisis de datos rápido.
Se presenta en un informe interactivo las siguientes estadísticas para cada columna.
Para este Notebook vamos a estar trabajando el dataset que se encuentra en el siguiente link: Caida de meteoritos
Este completo conjunto de datos de la Sociedad Meteorológica contiene información sobre todos los aterrizajes de meteoritos conocidos. Es interesante observar los lugares de la tierra donde han caido estos objetos, siguiendo las coordenadas del dataset.
Comencemos ahora importando el dataset, para poder entender un poco los datos que trabajaremos.
import pandas as pd
Hemos guardado el dataset (Caida de meteoritos) en la carpeta datasets
del presente entorno de trabajo, asi que seleccionamos la ruta adecuada para la importación
df = pd.read_csv("datasets/Meteorite_Landings.csv")
Y ahora revisamos los datos
df.head()
name | id | nametype | recclass | mass (g) | fall | year | reclat | reclong | GeoLocation | |
---|---|---|---|---|---|---|---|---|---|---|
0 | Aachen | 1 | Valid | L5 | 21.0 | Fell | 01/01/1880 12:00:00 AM | 50.77500 | 6.08333 | (50.775, 6.08333) |
1 | Aarhus | 2 | Valid | H6 | 720.0 | Fell | 01/01/1951 12:00:00 AM | 56.18333 | 10.23333 | (56.18333, 10.23333) |
2 | Abee | 6 | Valid | EH4 | 107000.0 | Fell | 01/01/1952 12:00:00 AM | 54.21667 | -113.00000 | (54.21667, -113.0) |
3 | Acapulco | 10 | Valid | Acapulcoite | 1914.0 | Fell | 01/01/1976 12:00:00 AM | 16.88333 | -99.90000 | (16.88333, -99.9) |
4 | Achiras | 370 | Valid | L6 | 780.0 | Fell | 01/01/1902 12:00:00 AM | -33.16667 | -64.95000 | (-33.16667, -64.95) |
df.shape
(45716, 10)
Es un dataset muy interesante, donde podemos observar el nombre que los científicos le dieron al meteorito, el tipo de clase recclass
, el peso en gramos mass (g)
la fecha en la que cayó y lad coordenadas en las cuales cayó.
También es importante notar que se trata de un dataset muy completo, con 45.716 registros y 10 columnas. Esta ifnromación es dada por el .shape
Para más información sobre el dataset puedes ingresar aqui: Meteoritos - Nasa
Ahora, como mencionamos al principio, con Pandas nos hemos acostumbrado a correr el comando .describe()
para generar un análisis descriptivo sobre el dataset en cuestión. Las estadísticas descriptivas incluyen las que resumen la tendencia central, la dispersión y la forma de la distribución de un conjunto de datos, excluyendo los valores NaN
.
Analiza tanto series numéricas como de objetos, así como conjuntos de columnas DataFrame de tipos de datos mixtos. El resultado variará en función de lo que se proporcione. Para más información sobre .describe()
y los parámetros que podemos pasar, aqui puede encontrar la info: Pandas describe
Ahora vamos a correr este comando sobre nuestro dataset y veamos el resultado
df.describe()
id | mass (g) | reclat | reclong | |
---|---|---|---|---|
count | 45716.000000 | 4.558500e+04 | 38401.000000 | 38401.000000 |
mean | 26889.735104 | 1.327808e+04 | -39.122580 | 61.074319 |
std | 16860.683030 | 5.749889e+05 | 46.378511 | 80.647298 |
min | 1.000000 | 0.000000e+00 | -87.366670 | -165.433330 |
25% | 12688.750000 | 7.200000e+00 | -76.714240 | 0.000000 |
50% | 24261.500000 | 3.260000e+01 | -71.500000 | 35.666670 |
75% | 40656.750000 | 2.026000e+02 | 0.000000 | 157.166670 |
max | 57458.000000 | 6.000000e+07 | 81.166670 | 354.473330 |
El describe omite las columnas categoricas (tipo string) y hace un análisis estadístico descriptivo sobre las columnas numéricas. Aquí podríamos ver que la columna id
podria no ser util para este analisis, ya que se trata solamente de un indicador unico para cada fila (llave primaria de la tabla), mientras que la masa si es útil e interesante entender, por ejemplo, el valor minimo y maximo, la media y los percentiles (25, 50, 75).
Como vemos, se trata de un análisis muy básico, sin más información relevante. Si queremos información más relevante debemos empezar a escribir código.
Aqui es donde viene Pandas profiling, y su utilidad. La documentación de esta librería la puedes encontrar en el siguiente enlace. Pandas profiling. La instalación la haremos de la siguiente manera
!pip3 install 'pandas-profiling[notebook,html]'
Collecting pandas-profiling[html,notebook] Using cached pandas_profiling-2.9.0-py2.py3-none-any.whl (258 kB) WARNING: pandas-profiling 2.9.0 does not provide the extra 'html' Collecting seaborn>=0.10.1 Using cached seaborn-0.11.0-py3-none-any.whl (283 kB) Requirement already satisfied: pandas!=1.0.0,!=1.0.1,!=1.0.2,!=1.1.0,>=0.25.3 in ./lib/python3.6/site-packages (from pandas-profiling[html,notebook]) (1.1.4) Requirement already satisfied: numpy>=1.16.0 in ./lib/python3.6/site-packages (from pandas-profiling[html,notebook]) (1.19.4) Collecting visions[type_image_path]==0.5.0 Using cached visions-0.5.0-py3-none-any.whl (64 kB) Processing /home/daniel/.cache/pip/wheels/c3/fe/0b/4450b38bceb9ae43dd7d0f16e353566f30f5f4d59a58eca2ed/htmlmin-0.1.12-py3-none-any.whl Requirement already satisfied: ipywidgets>=7.5.1 in ./lib/python3.6/site-packages (from pandas-profiling[html,notebook]) (7.5.1) Collecting requests>=2.23.0 Using cached requests-2.24.0-py2.py3-none-any.whl (61 kB) Collecting missingno>=0.4.2 Using cached missingno-0.4.2-py3-none-any.whl (9.7 kB) Collecting matplotlib>=3.2.0 Using cached matplotlib-3.3.2-cp36-cp36m-manylinux1_x86_64.whl (11.6 MB) Collecting joblib Downloading joblib-0.17.0-py3-none-any.whl (301 kB) |████████████████████████████████| 301 kB 1.2 MB/s eta 0:00:01 Collecting confuse>=1.0.0 Using cached confuse-1.3.0-py2.py3-none-any.whl (64 kB) Requirement already satisfied: attrs>=19.3.0 in ./lib/python3.6/site-packages (from pandas-profiling[html,notebook]) (20.3.0) Collecting tangled-up-in-unicode>=0.0.6 Using cached tangled_up_in_unicode-0.0.6-py3-none-any.whl (3.1 MB) Requirement already satisfied: jinja2>=2.11.1 in ./lib/python3.6/site-packages (from pandas-profiling[html,notebook]) (2.11.2) Collecting tqdm>=4.43.0 Downloading tqdm-4.51.0-py2.py3-none-any.whl (70 kB) |████████████████████████████████| 70 kB 660 kB/s eta 0:00:01 Collecting scipy>=1.4.1 Downloading scipy-1.5.4-cp36-cp36m-manylinux1_x86_64.whl (25.9 MB) |████████████████████████████████| 25.9 MB 8.5 MB/s eta 0:00:01 |██████████████████████████▋ | 21.5 MB 7.5 MB/s eta 0:00:01 Collecting phik>=0.9.10 Using cached phik-0.10.0-py3-none-any.whl (599 kB) Requirement already satisfied: jupyter-core>=4.6.3; extra == "notebook" in ./lib/python3.6/site-packages (from pandas-profiling[html,notebook]) (4.6.3) Requirement already satisfied: jupyter-client>=6.0.0; extra == "notebook" in ./lib/python3.6/site-packages (from pandas-profiling[html,notebook]) (6.1.7) Requirement already satisfied: pytz>=2017.2 in ./lib/python3.6/site-packages (from pandas!=1.0.0,!=1.0.1,!=1.0.2,!=1.1.0,>=0.25.3->pandas-profiling[html,notebook]) (2020.4) Requirement already satisfied: python-dateutil>=2.7.3 in ./lib/python3.6/site-packages (from pandas!=1.0.0,!=1.0.1,!=1.0.2,!=1.1.0,>=0.25.3->pandas-profiling[html,notebook]) (2.8.1) Collecting networkx>=2.4 Using cached networkx-2.5-py3-none-any.whl (1.6 MB) Collecting Pillow; extra == "type_image_path" Using cached Pillow-8.0.1-cp36-cp36m-manylinux1_x86_64.whl (2.2 MB) Processing /home/daniel/.cache/pip/wheels/43/f6/25/a58e553441acfc5ad7782c545147759c94d0d95ea1c1edd4bf/ImageHash-4.1.0-py2.py3-none-any.whl Requirement already satisfied: traitlets>=4.3.1 in ./lib/python3.6/site-packages (from ipywidgets>=7.5.1->pandas-profiling[html,notebook]) (4.3.3) Requirement already satisfied: ipython>=4.0.0; python_version >= "3.3" in ./lib/python3.6/site-packages (from ipywidgets>=7.5.1->pandas-profiling[html,notebook]) (7.16.1) Requirement already satisfied: nbformat>=4.2.0 in ./lib/python3.6/site-packages (from ipywidgets>=7.5.1->pandas-profiling[html,notebook]) (5.0.8) Requirement already satisfied: widgetsnbextension~=3.5.0 in ./lib/python3.6/site-packages (from ipywidgets>=7.5.1->pandas-profiling[html,notebook]) (3.5.1) Requirement already satisfied: ipykernel>=4.5.1 in ./lib/python3.6/site-packages (from ipywidgets>=7.5.1->pandas-profiling[html,notebook]) (5.3.4) Collecting certifi>=2017.4.17 Downloading certifi-2020.11.8-py2.py3-none-any.whl (155 kB) |████████████████████████████████| 155 kB 2.6 MB/s eta 0:00:01 Collecting idna<3,>=2.5 Using cached idna-2.10-py2.py3-none-any.whl (58 kB) Collecting chardet<4,>=3.0.2 Using cached chardet-3.0.4-py2.py3-none-any.whl (133 kB) Collecting urllib3!=1.25.0,!=1.25.1,<1.26,>=1.21.1 Downloading urllib3-1.25.11-py2.py3-none-any.whl (127 kB) |████████████████████████████████| 127 kB 4.2 MB/s eta 0:00:01 Collecting cycler>=0.10 Using cached cycler-0.10.0-py2.py3-none-any.whl (6.5 kB) Collecting kiwisolver>=1.0.1 Downloading kiwisolver-1.3.1-cp36-cp36m-manylinux1_x86_64.whl (1.1 MB) |████████████████████████████████| 1.1 MB 5.1 MB/s eta 0:00:01 Requirement already satisfied: pyparsing!=2.0.4,!=2.1.2,!=2.1.6,>=2.0.3 in ./lib/python3.6/site-packages (from matplotlib>=3.2.0->pandas-profiling[html,notebook]) (2.4.7) Processing /home/daniel/.cache/pip/wheels/e5/9d/ad/2ee53cf262cba1ffd8afe1487eef788ea3f260b7e6232a80fc/PyYAML-5.3.1-cp36-cp36m-linux_x86_64.whl Requirement already satisfied: MarkupSafe>=0.23 in ./lib/python3.6/site-packages (from jinja2>=2.11.1->pandas-profiling[html,notebook]) (1.1.1) Collecting numba>=0.38.1 Using cached numba-0.51.2-cp36-cp36m-manylinux2014_x86_64.whl (3.1 MB) Requirement already satisfied: pyzmq>=13 in ./lib/python3.6/site-packages (from jupyter-client>=6.0.0; extra == "notebook"->pandas-profiling[html,notebook]) (19.0.2) Requirement already satisfied: tornado>=4.1 in ./lib/python3.6/site-packages (from jupyter-client>=6.0.0; extra == "notebook"->pandas-profiling[html,notebook]) (6.1) Requirement already satisfied: six>=1.5 in ./lib/python3.6/site-packages (from python-dateutil>=2.7.3->pandas!=1.0.0,!=1.0.1,!=1.0.2,!=1.1.0,>=0.25.3->pandas-profiling[html,notebook]) (1.15.0) Requirement already satisfied: decorator>=4.3.0 in ./lib/python3.6/site-packages (from networkx>=2.4->visions[type_image_path]==0.5.0->pandas-profiling[html,notebook]) (4.4.2) Collecting PyWavelets Using cached PyWavelets-1.1.1-cp36-cp36m-manylinux1_x86_64.whl (4.4 MB) Requirement already satisfied: ipython-genutils in ./lib/python3.6/site-packages (from traitlets>=4.3.1->ipywidgets>=7.5.1->pandas-profiling[html,notebook]) (0.2.0) Requirement already satisfied: pygments in ./lib/python3.6/site-packages (from ipython>=4.0.0; python_version >= "3.3"->ipywidgets>=7.5.1->pandas-profiling[html,notebook]) (2.7.2) Requirement already satisfied: prompt-toolkit!=3.0.0,!=3.0.1,<3.1.0,>=2.0.0 in ./lib/python3.6/site-packages (from ipython>=4.0.0; python_version >= "3.3"->ipywidgets>=7.5.1->pandas-profiling[html,notebook]) (3.0.8) Requirement already satisfied: setuptools>=18.5 in ./lib/python3.6/site-packages (from ipython>=4.0.0; python_version >= "3.3"->ipywidgets>=7.5.1->pandas-profiling[html,notebook]) (50.2.0) Requirement already satisfied: backcall in ./lib/python3.6/site-packages (from ipython>=4.0.0; python_version >= "3.3"->ipywidgets>=7.5.1->pandas-profiling[html,notebook]) (0.2.0) Requirement already satisfied: jedi>=0.10 in ./lib/python3.6/site-packages (from ipython>=4.0.0; python_version >= "3.3"->ipywidgets>=7.5.1->pandas-profiling[html,notebook]) (0.17.2) Requirement already satisfied: pickleshare in ./lib/python3.6/site-packages (from ipython>=4.0.0; python_version >= "3.3"->ipywidgets>=7.5.1->pandas-profiling[html,notebook]) (0.7.5) Requirement already satisfied: pexpect; sys_platform != "win32" in ./lib/python3.6/site-packages (from ipython>=4.0.0; python_version >= "3.3"->ipywidgets>=7.5.1->pandas-profiling[html,notebook]) (4.8.0) Requirement already satisfied: jsonschema!=2.5.0,>=2.4 in ./lib/python3.6/site-packages (from nbformat>=4.2.0->ipywidgets>=7.5.1->pandas-profiling[html,notebook]) (3.2.0) Requirement already satisfied: notebook>=4.4.1 in ./lib/python3.6/site-packages (from widgetsnbextension~=3.5.0->ipywidgets>=7.5.1->pandas-profiling[html,notebook]) (6.1.5) Collecting llvmlite<0.35,>=0.34.0.dev0 Using cached llvmlite-0.34.0-cp36-cp36m-manylinux2010_x86_64.whl (24.6 MB) Requirement already satisfied: wcwidth in ./lib/python3.6/site-packages (from prompt-toolkit!=3.0.0,!=3.0.1,<3.1.0,>=2.0.0->ipython>=4.0.0; python_version >= "3.3"->ipywidgets>=7.5.1->pandas-profiling[html,notebook]) (0.2.5) Requirement already satisfied: parso<0.8.0,>=0.7.0 in ./lib/python3.6/site-packages (from jedi>=0.10->ipython>=4.0.0; python_version >= "3.3"->ipywidgets>=7.5.1->pandas-profiling[html,notebook]) (0.7.1) Requirement already satisfied: ptyprocess>=0.5 in ./lib/python3.6/site-packages (from pexpect; sys_platform != "win32"->ipython>=4.0.0; python_version >= "3.3"->ipywidgets>=7.5.1->pandas-profiling[html,notebook]) (0.6.0) Requirement already satisfied: importlib-metadata; python_version < "3.8" in ./lib/python3.6/site-packages (from jsonschema!=2.5.0,>=2.4->nbformat>=4.2.0->ipywidgets>=7.5.1->pandas-profiling[html,notebook]) (2.0.0) Requirement already satisfied: pyrsistent>=0.14.0 in ./lib/python3.6/site-packages (from jsonschema!=2.5.0,>=2.4->nbformat>=4.2.0->ipywidgets>=7.5.1->pandas-profiling[html,notebook]) (0.17.3) Requirement already satisfied: prometheus-client in ./lib/python3.6/site-packages (from notebook>=4.4.1->widgetsnbextension~=3.5.0->ipywidgets>=7.5.1->pandas-profiling[html,notebook]) (0.8.0) Requirement already satisfied: Send2Trash in ./lib/python3.6/site-packages (from notebook>=4.4.1->widgetsnbextension~=3.5.0->ipywidgets>=7.5.1->pandas-profiling[html,notebook]) (1.5.0) Requirement already satisfied: terminado>=0.8.3 in ./lib/python3.6/site-packages (from notebook>=4.4.1->widgetsnbextension~=3.5.0->ipywidgets>=7.5.1->pandas-profiling[html,notebook]) (0.9.1) Requirement already satisfied: argon2-cffi in ./lib/python3.6/site-packages (from notebook>=4.4.1->widgetsnbextension~=3.5.0->ipywidgets>=7.5.1->pandas-profiling[html,notebook]) (20.1.0) Requirement already satisfied: nbconvert in ./lib/python3.6/site-packages (from notebook>=4.4.1->widgetsnbextension~=3.5.0->ipywidgets>=7.5.1->pandas-profiling[html,notebook]) (6.0.7) Requirement already satisfied: zipp>=0.5 in ./lib/python3.6/site-packages (from importlib-metadata; python_version < "3.8"->jsonschema!=2.5.0,>=2.4->nbformat>=4.2.0->ipywidgets>=7.5.1->pandas-profiling[html,notebook]) (3.4.0) Requirement already satisfied: cffi>=1.0.0 in ./lib/python3.6/site-packages (from argon2-cffi->notebook>=4.4.1->widgetsnbextension~=3.5.0->ipywidgets>=7.5.1->pandas-profiling[html,notebook]) (1.14.3) Requirement already satisfied: nbclient<0.6.0,>=0.5.0 in ./lib/python3.6/site-packages (from nbconvert->notebook>=4.4.1->widgetsnbextension~=3.5.0->ipywidgets>=7.5.1->pandas-profiling[html,notebook]) (0.5.1) Requirement already satisfied: bleach in ./lib/python3.6/site-packages (from nbconvert->notebook>=4.4.1->widgetsnbextension~=3.5.0->ipywidgets>=7.5.1->pandas-profiling[html,notebook]) (3.2.1) Requirement already satisfied: entrypoints>=0.2.2 in ./lib/python3.6/site-packages (from nbconvert->notebook>=4.4.1->widgetsnbextension~=3.5.0->ipywidgets>=7.5.1->pandas-profiling[html,notebook]) (0.3) Requirement already satisfied: testpath in ./lib/python3.6/site-packages (from nbconvert->notebook>=4.4.1->widgetsnbextension~=3.5.0->ipywidgets>=7.5.1->pandas-profiling[html,notebook]) (0.4.4) Requirement already satisfied: defusedxml in ./lib/python3.6/site-packages (from nbconvert->notebook>=4.4.1->widgetsnbextension~=3.5.0->ipywidgets>=7.5.1->pandas-profiling[html,notebook]) (0.6.0) Requirement already satisfied: pandocfilters>=1.4.1 in ./lib/python3.6/site-packages (from nbconvert->notebook>=4.4.1->widgetsnbextension~=3.5.0->ipywidgets>=7.5.1->pandas-profiling[html,notebook]) (1.4.3) Requirement already satisfied: mistune<2,>=0.8.1 in ./lib/python3.6/site-packages (from nbconvert->notebook>=4.4.1->widgetsnbextension~=3.5.0->ipywidgets>=7.5.1->pandas-profiling[html,notebook]) (0.8.4) Requirement already satisfied: jupyterlab-pygments in ./lib/python3.6/site-packages (from nbconvert->notebook>=4.4.1->widgetsnbextension~=3.5.0->ipywidgets>=7.5.1->pandas-profiling[html,notebook]) (0.1.2) Requirement already satisfied: pycparser in ./lib/python3.6/site-packages (from cffi>=1.0.0->argon2-cffi->notebook>=4.4.1->widgetsnbextension~=3.5.0->ipywidgets>=7.5.1->pandas-profiling[html,notebook]) (2.20) Requirement already satisfied: nest-asyncio in ./lib/python3.6/site-packages (from nbclient<0.6.0,>=0.5.0->nbconvert->notebook>=4.4.1->widgetsnbextension~=3.5.0->ipywidgets>=7.5.1->pandas-profiling[html,notebook]) (1.4.2) Requirement already satisfied: async-generator in ./lib/python3.6/site-packages (from nbclient<0.6.0,>=0.5.0->nbconvert->notebook>=4.4.1->widgetsnbextension~=3.5.0->ipywidgets>=7.5.1->pandas-profiling[html,notebook]) (1.10) Requirement already satisfied: packaging in ./lib/python3.6/site-packages (from bleach->nbconvert->notebook>=4.4.1->widgetsnbextension~=3.5.0->ipywidgets>=7.5.1->pandas-profiling[html,notebook]) (20.4) Requirement already satisfied: webencodings in ./lib/python3.6/site-packages (from bleach->nbconvert->notebook>=4.4.1->widgetsnbextension~=3.5.0->ipywidgets>=7.5.1->pandas-profiling[html,notebook]) (0.5.1) Installing collected packages: scipy, cycler, certifi, kiwisolver, Pillow, matplotlib, seaborn, tangled-up-in-unicode, networkx, PyWavelets, imagehash, visions, htmlmin, idna, chardet, urllib3, requests, missingno, joblib, pyyaml, confuse, tqdm, llvmlite, numba, phik, pandas-profiling Successfully installed Pillow-8.0.1 PyWavelets-1.1.1 certifi-2020.11.8 chardet-3.0.4 confuse-1.3.0 cycler-0.10.0 htmlmin-0.1.12 idna-2.10 imagehash-4.1.0 joblib-0.17.0 kiwisolver-1.3.1 llvmlite-0.34.0 matplotlib-3.3.2 missingno-0.4.2 networkx-2.5 numba-0.51.2 pandas-profiling-2.9.0 phik-0.10.0 pyyaml-5.3.1 requests-2.24.0 scipy-1.5.4 seaborn-0.11.0 tangled-up-in-unicode-0.0.6 tqdm-4.51.0 urllib3-1.25.11 visions-0.5.0 WARNING: You are using pip version 20.2.3; however, version 20.2.4 is available. You should consider upgrading via the '/home/daniel/Desktop/narrativetext_project/notebooks/bin/python -m pip install --upgrade pip' command.
Es obligatorio pasar entre comillas el comando y seguido de notebook,html
esto es debido a que necesitaremos estas dos funciones de la librearía.
Si estás usando Conda, estas son las otras formas de instalarlo: Instalando Pandas profiling
Ahora vamos a crear una serie de columnas que serán relevantes para el analisis que haremos con Pandas Profiling, la primera de ellas sera crear una variable constante para todos los registros, en esta ocasión diremos que todos los registros pertenecen a la NASA, y hacemos lo siguiente
df['source'] = "NASA"
df.head()
name | id | nametype | recclass | mass (g) | fall | year | reclat | reclong | GeoLocation | source | |
---|---|---|---|---|---|---|---|---|---|---|---|
0 | Aachen | 1 | Valid | L5 | 21.0 | Fell | 01/01/1880 12:00:00 AM | 50.77500 | 6.08333 | (50.775, 6.08333) | NASA |
1 | Aarhus | 2 | Valid | H6 | 720.0 | Fell | 01/01/1951 12:00:00 AM | 56.18333 | 10.23333 | (56.18333, 10.23333) | NASA |
2 | Abee | 6 | Valid | EH4 | 107000.0 | Fell | 01/01/1952 12:00:00 AM | 54.21667 | -113.00000 | (54.21667, -113.0) | NASA |
3 | Acapulco | 10 | Valid | Acapulcoite | 1914.0 | Fell | 01/01/1976 12:00:00 AM | 16.88333 | -99.90000 | (16.88333, -99.9) | NASA |
4 | Achiras | 370 | Valid | L6 | 780.0 | Fell | 01/01/1902 12:00:00 AM | -33.16667 | -64.95000 | (-33.16667, -64.95) | NASA |
Como vemos, al final se creó dicha columna. Vamos a crear ahora una variable booleana de forma aleatoria, simulando algún tipo de salida booleana para cada registro.
Recordemos que esto se hace con el fin de que nuestro análisis exploratorio pueda identificar este tipo de datos en el resultado.
# importamos numpy, debería haber quedado instalado con el Pandas. Si no lo tienes, puedes hacerlo con
# el comando pip3 install numpy
import numpy as np
# numpy nos va a ayudar a crear esos booleanos aleatorios en la siguiente linea de código
df['boolean'] = np.random.choice([True, False], df.shape[0])
df.head()
name | id | nametype | recclass | mass (g) | fall | year | reclat | reclong | GeoLocation | source | boolean | |
---|---|---|---|---|---|---|---|---|---|---|---|---|
0 | Aachen | 1 | Valid | L5 | 21.0 | Fell | 01/01/1880 12:00:00 AM | 50.77500 | 6.08333 | (50.775, 6.08333) | NASA | True |
1 | Aarhus | 2 | Valid | H6 | 720.0 | Fell | 01/01/1951 12:00:00 AM | 56.18333 | 10.23333 | (56.18333, 10.23333) | NASA | False |
2 | Abee | 6 | Valid | EH4 | 107000.0 | Fell | 01/01/1952 12:00:00 AM | 54.21667 | -113.00000 | (54.21667, -113.0) | NASA | False |
3 | Acapulco | 10 | Valid | Acapulcoite | 1914.0 | Fell | 01/01/1976 12:00:00 AM | 16.88333 | -99.90000 | (16.88333, -99.9) | NASA | True |
4 | Achiras | 370 | Valid | L6 | 780.0 | Fell | 01/01/1902 12:00:00 AM | -33.16667 | -64.95000 | (-33.16667, -64.95) | NASA | True |
Como vemos se creo la columna boolean
con valores aleatorios de True
o False
para cada una de las filas de nuestro dataset, esto es gracias al df.shape[0]
que hace referencia a las filas o registros del dataset, o sea que hizo esta operación 45.716 veces, el cual es el numero total de registros.
Hagamos ahora algo similar, pero mezclando tipos de datos numéricos y tipos de datos categóricos (strings)
df['mixed'] = np.random.choice([1, 'A'], df.shape[0])
df.head()
name | id | nametype | recclass | mass (g) | fall | year | reclat | reclong | GeoLocation | source | boolean | mixed | |
---|---|---|---|---|---|---|---|---|---|---|---|---|---|
0 | Aachen | 1 | Valid | L5 | 21.0 | Fell | 01/01/1880 12:00:00 AM | 50.77500 | 6.08333 | (50.775, 6.08333) | NASA | True | A |
1 | Aarhus | 2 | Valid | H6 | 720.0 | Fell | 01/01/1951 12:00:00 AM | 56.18333 | 10.23333 | (56.18333, 10.23333) | NASA | False | A |
2 | Abee | 6 | Valid | EH4 | 107000.0 | Fell | 01/01/1952 12:00:00 AM | 54.21667 | -113.00000 | (54.21667, -113.0) | NASA | False | A |
3 | Acapulco | 10 | Valid | Acapulcoite | 1914.0 | Fell | 01/01/1976 12:00:00 AM | 16.88333 | -99.90000 | (16.88333, -99.9) | NASA | True | 1 |
4 | Achiras | 370 | Valid | L6 | 780.0 | Fell | 01/01/1902 12:00:00 AM | -33.16667 | -64.95000 | (-33.16667, -64.95) | NASA | True | 1 |
Como vemos, aqui estamos simulando que una columna tiene dos tipos de datos mezclados, tanto numericos como categoricos. Esto es algo que podemos encontrar en los datasets reales, y describe()
de Pandas simplemente los va a ignorar, y no nos dará ningun analisis sobre esa columna (recuerden que describe()
solo arroja resultados sobre columnas numéricas, incluso ignora también las columnas booleanas)
Vamos ahora a hacer algo más interesante aún. Vamos a crear una nueva columna simulando una alta correlación con una columna existente. Particularmente lo haremos sobre la columna reclat
que habla de la latitud donde ha caido el meteorito, y le sumamos una distribución normal con una desviación estandar de 5
y de un tamaño de muestras igual a la longitud del dataset.
Si quieres ver como se crea una simulación de una distribucion normal con numeros aleatorios, revisa este link. Random normal numpy
df['reclat_city'] = df['reclat'] + np.random.normal(scale=5, size=(len(df)))
df
name | id | nametype | recclass | mass (g) | fall | year | reclat | reclong | GeoLocation | source | boolean | mixed | reclat_city | |
---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|
0 | Aachen | 1 | Valid | L5 | 21.0 | Fell | 01/01/1880 12:00:00 AM | 50.77500 | 6.08333 | (50.775, 6.08333) | NASA | True | A | 56.089190 |
1 | Aarhus | 2 | Valid | H6 | 720.0 | Fell | 01/01/1951 12:00:00 AM | 56.18333 | 10.23333 | (56.18333, 10.23333) | NASA | False | A | 53.384628 |
2 | Abee | 6 | Valid | EH4 | 107000.0 | Fell | 01/01/1952 12:00:00 AM | 54.21667 | -113.00000 | (54.21667, -113.0) | NASA | False | A | 60.518163 |
3 | Acapulco | 10 | Valid | Acapulcoite | 1914.0 | Fell | 01/01/1976 12:00:00 AM | 16.88333 | -99.90000 | (16.88333, -99.9) | NASA | True | 1 | 8.132706 |
4 | Achiras | 370 | Valid | L6 | 780.0 | Fell | 01/01/1902 12:00:00 AM | -33.16667 | -64.95000 | (-33.16667, -64.95) | NASA | True | 1 | -31.859416 |
... | ... | ... | ... | ... | ... | ... | ... | ... | ... | ... | ... | ... | ... | ... |
45711 | Zillah 002 | 31356 | Valid | Eucrite | 172.0 | Found | 01/01/1990 12:00:00 AM | 29.03700 | 17.01850 | (29.037, 17.0185) | NASA | False | 1 | 32.763734 |
45712 | Zinder | 30409 | Valid | Pallasite, ungrouped | 46.0 | Found | 01/01/1999 12:00:00 AM | 13.78333 | 8.96667 | (13.78333, 8.96667) | NASA | True | 1 | 14.006757 |
45713 | Zlin | 30410 | Valid | H4 | 3.3 | Found | 01/01/1939 12:00:00 AM | 49.25000 | 17.66667 | (49.25, 17.66667) | NASA | False | 1 | 51.618411 |
45714 | Zubkovsky | 31357 | Valid | L6 | 2167.0 | Found | 01/01/2003 12:00:00 AM | 49.78917 | 41.50460 | (49.78917, 41.5046) | NASA | False | 1 | 46.240257 |
45715 | Zulu Queen | 30414 | Valid | L3.7 | 200.0 | Found | 01/01/1976 12:00:00 AM | 33.98333 | -115.68333 | (33.98333, -115.68333) | NASA | False | 1 | 25.042984 |
45716 rows × 14 columns
Revisemos el resultado del último comando, podemos ver que esta columna reclat_city
ahora tiene una alta correlacion con reclat
, porque cuando una observación o fila es positiva la otra también, y cuando una es negativa, la otra también.
Para analizar correlaciones con Pandas usamos un método diferente al describe()
, en este caso usamos el comando corr()
. Sin embargo, con Pandas profiling ambos análisis (estadística descriptiva y correlaciones) los obtendremos con un solo comando. Lo veremos en unos momentos cuando corramos nuestro análisis exploratorio.
Recuerden que por ahora lo que estamos haciendo es añadiendo columnas al dataframe con el fin de ver todas las posibilidades que nos ofrece la herramienta de Pandas profiling.
Vamos a simular ahora otro escenario común en los datasets, y es tener observaciones o filas duplicadas. Esto lo haremos asi:
duplicates_to_add = pd.DataFrame(df.iloc[0:10])
duplicates_to_add
name | id | nametype | recclass | mass (g) | fall | year | reclat | reclong | GeoLocation | source | boolean | mixed | reclat_city | |
---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|
0 | Aachen | 1 | Valid | L5 | 21.0 | Fell | 01/01/1880 12:00:00 AM | 50.77500 | 6.08333 | (50.775, 6.08333) | NASA | True | A | 56.089190 |
1 | Aarhus | 2 | Valid | H6 | 720.0 | Fell | 01/01/1951 12:00:00 AM | 56.18333 | 10.23333 | (56.18333, 10.23333) | NASA | False | A | 53.384628 |
2 | Abee | 6 | Valid | EH4 | 107000.0 | Fell | 01/01/1952 12:00:00 AM | 54.21667 | -113.00000 | (54.21667, -113.0) | NASA | False | A | 60.518163 |
3 | Acapulco | 10 | Valid | Acapulcoite | 1914.0 | Fell | 01/01/1976 12:00:00 AM | 16.88333 | -99.90000 | (16.88333, -99.9) | NASA | True | 1 | 8.132706 |
4 | Achiras | 370 | Valid | L6 | 780.0 | Fell | 01/01/1902 12:00:00 AM | -33.16667 | -64.95000 | (-33.16667, -64.95) | NASA | True | 1 | -31.859416 |
5 | Adhi Kot | 379 | Valid | EH4 | 4239.0 | Fell | 01/01/1919 12:00:00 AM | 32.10000 | 71.80000 | (32.1, 71.8) | NASA | True | 1 | 37.991494 |
6 | Adzhi-Bogdo (stone) | 390 | Valid | LL3-6 | 910.0 | Fell | 01/01/1949 12:00:00 AM | 44.83333 | 95.16667 | (44.83333, 95.16667) | NASA | False | 1 | 47.711677 |
7 | Agen | 392 | Valid | H5 | 30000.0 | Fell | 01/01/1814 12:00:00 AM | 44.21667 | 0.61667 | (44.21667, 0.61667) | NASA | True | 1 | 42.067262 |
8 | Aguada | 398 | Valid | L6 | 1620.0 | Fell | 01/01/1930 12:00:00 AM | -31.60000 | -65.23333 | (-31.6, -65.23333) | NASA | False | 1 | -35.545861 |
9 | Aguila Blanca | 417 | Valid | L | 1440.0 | Fell | 01/01/1920 12:00:00 AM | -30.86667 | -64.55000 | (-30.86667, -64.55) | NASA | True | A | -30.305074 |
Lo que acabamos de hacer fue crear un nuevo dataframe a partir de las 10 primeras filas de nuestro dataframe original. Para ello usamos un iloc
que sirve para seleccionar filas y un selector tipo slice para seleccionar de la fila cero a la fila 10-1.
Ahora cambiemos el nombre para identificarlas posteriormente, pero los demás valores quedan igual
duplicates_to_add['name'] = duplicates_to_add['name'] + " copy"
duplicates_to_add
name | id | nametype | recclass | mass (g) | fall | year | reclat | reclong | GeoLocation | source | boolean | mixed | reclat_city | |
---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|
0 | Aachen copy | 1 | Valid | L5 | 21.0 | Fell | 01/01/1880 12:00:00 AM | 50.77500 | 6.08333 | (50.775, 6.08333) | NASA | True | A | 56.089190 |
1 | Aarhus copy | 2 | Valid | H6 | 720.0 | Fell | 01/01/1951 12:00:00 AM | 56.18333 | 10.23333 | (56.18333, 10.23333) | NASA | False | A | 53.384628 |
2 | Abee copy | 6 | Valid | EH4 | 107000.0 | Fell | 01/01/1952 12:00:00 AM | 54.21667 | -113.00000 | (54.21667, -113.0) | NASA | False | A | 60.518163 |
3 | Acapulco copy | 10 | Valid | Acapulcoite | 1914.0 | Fell | 01/01/1976 12:00:00 AM | 16.88333 | -99.90000 | (16.88333, -99.9) | NASA | True | 1 | 8.132706 |
4 | Achiras copy | 370 | Valid | L6 | 780.0 | Fell | 01/01/1902 12:00:00 AM | -33.16667 | -64.95000 | (-33.16667, -64.95) | NASA | True | 1 | -31.859416 |
5 | Adhi Kot copy | 379 | Valid | EH4 | 4239.0 | Fell | 01/01/1919 12:00:00 AM | 32.10000 | 71.80000 | (32.1, 71.8) | NASA | True | 1 | 37.991494 |
6 | Adzhi-Bogdo (stone) copy | 390 | Valid | LL3-6 | 910.0 | Fell | 01/01/1949 12:00:00 AM | 44.83333 | 95.16667 | (44.83333, 95.16667) | NASA | False | 1 | 47.711677 |
7 | Agen copy | 392 | Valid | H5 | 30000.0 | Fell | 01/01/1814 12:00:00 AM | 44.21667 | 0.61667 | (44.21667, 0.61667) | NASA | True | 1 | 42.067262 |
8 | Aguada copy | 398 | Valid | L6 | 1620.0 | Fell | 01/01/1930 12:00:00 AM | -31.60000 | -65.23333 | (-31.6, -65.23333) | NASA | False | 1 | -35.545861 |
9 | Aguila Blanca copy | 417 | Valid | L | 1440.0 | Fell | 01/01/1920 12:00:00 AM | -30.86667 | -64.55000 | (-30.86667, -64.55) | NASA | True | A | -30.305074 |
Si vemos, ahora todos los nombres tienen la plabra copy
al final. Ya tenemos este nuevo dataset listo para concatenarlo al dataset original, y asi poder tener datos duplicados. Hagamos ahora el append
df = df.append(duplicates_to_add, ignore_index=True)
df.head()
name | id | nametype | recclass | mass (g) | fall | year | reclat | reclong | GeoLocation | source | boolean | mixed | reclat_city | |
---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|
0 | Aachen copy | 1 | Valid | L5 | 21.0 | Fell | 01/01/1880 12:00:00 AM | 50.77500 | 6.08333 | (50.775, 6.08333) | NASA | True | A | 56.089190 |
1 | Aarhus copy | 2 | Valid | H6 | 720.0 | Fell | 01/01/1951 12:00:00 AM | 56.18333 | 10.23333 | (56.18333, 10.23333) | NASA | False | A | 53.384628 |
2 | Abee copy | 6 | Valid | EH4 | 107000.0 | Fell | 01/01/1952 12:00:00 AM | 54.21667 | -113.00000 | (54.21667, -113.0) | NASA | False | A | 60.518163 |
3 | Acapulco copy | 10 | Valid | Acapulcoite | 1914.0 | Fell | 01/01/1976 12:00:00 AM | 16.88333 | -99.90000 | (16.88333, -99.9) | NASA | True | 1 | 8.132706 |
4 | Achiras copy | 370 | Valid | L6 | 780.0 | Fell | 01/01/1902 12:00:00 AM | -33.16667 | -64.95000 | (-33.16667, -64.95) | NASA | True | 1 | -31.859416 |
df.shape
(45726, 14)
El dataset original contiene 45716 filas, ahora tenemos 10 filas más, que son las filas duplicadas. De hecho podemos ver algunas de ellas en la visualización anterior!
Ahora si hemos llegado al momento esperado, hemos añadido unas columnas al dataset que permitirán ver análisis interesantes sobre él. Pero antes de ello, debemos ser justos con el pandas describe()
y mirar que análisis nos entrega sobre el dataset resultante
df.describe()
id | mass (g) | reclat | reclong | reclat_city | |
---|---|---|---|---|---|
count | 45726.000000 | 4.559500e+04 | 38411.000000 | 38411.000000 | 38411.000000 |
mean | 26883.906202 | 1.327843e+04 | -39.107095 | 61.052594 | -39.083521 |
std | 16863.445566 | 5.749260e+05 | 46.386011 | 80.655258 | 46.632457 |
min | 1.000000 | 0.000000e+00 | -87.366670 | -165.433330 | -103.454350 |
25% | 12681.250000 | 7.200000e+00 | -76.713770 | 0.000000 | -78.169548 |
50% | 24256.500000 | 3.261000e+01 | -71.500000 | 35.666670 | -68.832159 |
75% | 40653.500000 | 2.029000e+02 | 0.000000 | 157.166670 | 4.816108 |
max | 57458.000000 | 6.000000e+07 | 81.166670 | 354.473330 | 87.086560 |
Como vemos, muy poca diferencia, no nos entrega información adicional sobre:
Aquí es donde Pandas profiling brilla por su sencillez para realizar un análisis exploratorio sobre nuestros datasets. Sin más corramos el siguiente comando
## ya tenemos instalada la libreria, ahora nos falta importarla
import pandas_profiling
from pandas_profiling.utils.cache import cache_file
## Ahora corremos el reporte
report = df.profile_report(sort='None', html={'style':{'full_width':True}})
report
La salida habla por si sola. En comparación con Pandas describe()
o incluso con Pandas corr()
es bastante significativa, y de entrada podemos observar un montón de datos y análisis adicionales que nos ayadarán a interpretar mejor el dataset con el cual estamos trabajando. analicemos por ejemplo las columnas que recientemente agregamos
Duplicate rows 10
BOOL 1
reclat_city is highly correlated with reclat High correlation
mixed
podemos ver el análisis de los valores generados aleatoriamenteInteractions
podemos ver los diferentes tipos de gráficos y sus correlaciones entre las variables.Para más un an{alisis más detallado sobre los resultados obtenidos con este dataset usando Pandas profiling, porfaor vaya a este link. ¿Cómo Usar Pandas Profiling Para Análisis Exploratorio de Datos?
Finalmente podríamos querer tener este informe en un formato diferente a un Jupyter Notebook, la librería nos ofrece la posibilidad de exportar el informe a html
, lo cuál es útil para mostrarlo en un ambiente más amigable para el usuario final. En el cual puede incluso interactuar por medio de barras de navegación.
report.to_file(output_file="meteoritos_report_eda.html")
El archivo en este caso ha quedado dentro de mi ambiente de trabajo
Si le damos click se abrirá en el navegador. Este formato, personalmente me gusta bastante, ya que no influye el código, sino que se puede navegar a traves del análisis y mostrarlo a los stackeholders interesados en el análisis y tomar decisiones con base en ellos.
Como ven, es muy facil de utilizar la herramienta, y es un primer paso antes de empezar a realizar feature engineering y predicciones. Sin embargo existen algunas desventajas sobre la herramienta que son importantes tenerlo en cuenta:
data = df.sample(n=1000)
data.head()
name | id | nametype | recclass | mass (g) | fall | year | reclat | reclong | GeoLocation | source | boolean | mixed | reclat_city | |
---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|
2144 | Allan Hills 85081 | 947 | Valid | H6 | 12.2 | Found | 01/01/1985 12:00:00 AM | -76.92089 | 156.62782 | (-76.92089, 156.62782) | NASA | True | A | -75.659200 |
21735 | Lucerne Valley 012 | 14736 | Valid | H6 | 1.2 | Found | 01/01/1968 12:00:00 AM | 34.50000 | -116.95000 | (34.5, -116.95) | NASA | False | 1 | 39.188699 |
35370 | Queen Alexandra Range 99742 | 22188 | Valid | LL5 | 0.5 | Found | 01/01/1999 12:00:00 AM | -84.00000 | 168.00000 | (-84.0, 168.0) | NASA | True | 1 | -83.588141 |
42811 | Yamato 86043 | 29549 | Valid | H5 | 2.2 | Found | 01/01/1986 12:00:00 AM | -71.50000 | 35.66667 | (-71.5, 35.66667) | NASA | False | 1 | -60.951131 |
35511 | Queen Alexandra Range 99884 | 22328 | Valid | LL6 | 4.9 | Found | 01/01/1999 12:00:00 AM | -84.00000 | 168.00000 | (-84.0, 168.0) | NASA | False | 1 | -77.707680 |
len(data)
1000
Como vemos se han seleccionado 1000 muestras al azar, por tanto no hará el análisis sobre más de 40.000 muestras. Si tenemos, digamos 1.000.000 de muestras, la diferencia en rendimiento será notable, por ello esta sería una buena práctica
profile_in_sample = data.profile_report(sort='None', html={'style':{'full_width':True}})
profile_in_sample
Como vemos toma menos tiempo de ejecutarse con una muestra de 1.000 ejemplos.
profile_min = data.profile_report(minimal=True)
profile_min
Como podemos ver, es un reporte más rápido pero con menos información sobre el análsis exploratorio de los datos. Dejámos a tu disposición la decisión sobre el tipo de ifnrome que quieres generar. Si quieres ver funcionalidades más avanzadas de la librería porfavor dirijete al siguiente link: Pandas profiling avanzado
Esperamos te haya gustado este Notebook, nos vemos en el próximo!