{ "cells": [ { "cell_type": "markdown", "metadata": {}, "source": [ "# DataFrames de `pandas`\n", "\n", "Pandas es una librería de Python para **leer, manipular y analizar datos** que se combina muy bien con NumPy y Matplotlib\n", "\n", "Pandas nos provee de: \n", "\n", "- Dos nuevas estructuras de datos: `DataFrame` y `Series`\n", "- Herramientas de análisis de datos que operan sobre estas estructuras de datos\n", "\n", "A continuación nos enfocaremos en la creación y manipulación de objetos tipo `DataFrame`" ] }, { "cell_type": "code", "execution_count": 1, "metadata": {}, "outputs": [ { "data": { "text/plain": [ "'1.4.1'" ] }, "execution_count": 1, "metadata": {}, "output_type": "execute_result" } ], "source": [ "import pandas as pd\n", "pd.__version__" ] }, { "cell_type": "markdown", "metadata": {}, "source": [ "## Objeto `pandas.DataFrame`\n", "\n", "El objeto [`pandas.DataFrame`](https://pandas.pydata.org/pandas-docs/stable/reference/api/pandas.DataFrame.html) es la estructura de datos principal de pandas\n", "\n", "- Es un arreglo de dos dimensiones (matriz) que **representa una tabla**\n", "- Las filas y columnas de la tabla se identifican con un índice etiquetado denominado *label*\n", "- Cada columna puede tener tipo distinto \n", "\n", "**Ejemplo:** Considere el siguiente `DataFrame` que representa datos de la bolsa de Santiago:\n", "\n", "| | Monto \\[M$\\] | Variación \\[\\%\\] |\n", "|---|---|---|\n", "| AGUAS-A \t| 1653 | 0.36 |\n", "| BSANTANDER |\t 3531 | -0.31 |\n", "| CMPC \t| 5998 | -0.6 |\n", "| ENTEL | 1408 | 0.0 |\n", "\n", "En este ejemplo\n", "\n", "- La etiqueta de las filas son los nombres de las empresas\n", "- La etiqueta de las columnas son los nombres de los atributos medidas por la bolsa\n", "- El atributo *Monto* es un entero\n", "- El atributo *Variación* es un flotante\n", "\n", "A continuación veremos como crear y manipular objetos `DataFrame`" ] }, { "cell_type": "markdown", "metadata": {}, "source": [ "**Construcción de un `DataFrame`** \n", "\n", "Se usa el constructor\n", "\n", "```python\n", "pandas.DataFrame(data=None, \n", " index: Optional[Collection] = None, \n", " columns: Optional[Collection] = None, \n", " dtype: Union[str, numpy.dtype, ExtensionDtype, None] = None, \n", " copy: bool = False)\n", "```\n", "\n", "El argumento más importante es `data`, que puede ser un iterable (lista), un diccionario, un ndarray, entre otros\n", "\n", "Consideremos lo siguiente\n", "\n", "- Si usamos un diccionario los `keys` se interpretan como etiquetas de columnas\n", "- Si usamos un diccionario de diccionarios los `keys` más internos se interpretan como etiquetas de filas\n", "- Si usamos un `ndarray` y no especificamos `index` y/o `columns` se crean etiquetas por defecto\n", "\n", "A continuación se muestra como se crea un DataFrame en base a un diccionario" ] }, { "cell_type": "code", "execution_count": 2, "metadata": {}, "outputs": [ { "data": { "text/html": [ "
\n", "\n", "\n", " \n", " \n", " \n", " \n", " \n", " \n", " \n", " \n", " \n", " \n", " \n", " \n", " \n", " \n", " \n", " \n", " \n", " \n", " \n", " \n", " \n", " \n", " \n", " \n", " \n", " \n", " \n", " \n", " \n", "
Monto [M$]Variación [%]
AGUAS-A16530.36
BSANTANDER33510.31
CMPC5998-0.60
ENTEL14080.00
\n", "
" ], "text/plain": [ " Monto [M$] Variación [%]\n", "AGUAS-A 1653 0.36\n", "BSANTANDER 3351 0.31\n", "CMPC 5998 -0.60\n", "ENTEL 1408 0.00" ] }, "execution_count": 2, "metadata": {}, "output_type": "execute_result" } ], "source": [ "names = [\"AGUAS-A\", \"BSANTANDER\", \"CMPC\", \"ENTEL\"]\n", "\n", "ventas = {\n", " 'Monto [M$]': [1653, 3351, 5998, 1408],\n", " 'Variación [%]': [0.36, 0.31, -0.6, 0.0]\n", "}\n", "\n", "df = pd.DataFrame(ventas, index=names)\n", "df" ] }, { "cell_type": "markdown", "metadata": {}, "source": [ "**Atributos básicos de un `DataFrame`**\n", "\n", "Una vez que haz creado un DataFrame es conveniente revisarlo\n", "\n", "Si tu DataFrame se llama `df`, puedes usar\n", "\n", "- `df.head(5)` y `df.tail(5)` para imprimir las 5 primeras y 5 últimas filas, respectivamente\n", "- `df.columns` para recuperar un objeto `pandas.Index` con las etiquetas de columna\n", "- `df.index` para recuperar un objeto `pandas.Index` con las etiquetas de fila\n", "- `df.shape` para recuperar una tupla con el número de filas y número de columnas\n", "- `df.dtypes` para recuperar una lista con los tipos asignados a cada columna\n", "- `df.info()` nos da un resumen de lo anterior\n" ] }, { "cell_type": "markdown", "metadata": {}, "source": [ "Por ejemplo:" ] }, { "cell_type": "code", "execution_count": 3, "metadata": {}, "outputs": [ { "name": "stdout", "output_type": "stream", "text": [ "\n", "Index: 4 entries, AGUAS-A to ENTEL\n", "Data columns (total 2 columns):\n", " # Column Non-Null Count Dtype \n", "--- ------ -------------- ----- \n", " 0 Monto [M$] 4 non-null int64 \n", " 1 Variación [%] 4 non-null float64\n", "dtypes: float64(1), int64(1)\n", "memory usage: 96.0+ bytes\n" ] } ], "source": [ "df.info()" ] }, { "cell_type": "markdown", "metadata": {}, "source": [ "## Indexación y *slicing* de DataFrames\n", "\n", "**Indexado en base a etiquetas**\n", "\n", "Podemos usar las etiquetas para indexar un DataFrame ya sea en sus filas o en sus columnas\n", "\n", "También podemos hacer *slicing*, es decir recuperar un subconjunto de un DataFrame\n", "\n", ":::{important}\n", "\n", "Se usa la función `loc[]` para indexar y hacer slicing **en base a la etiqueta**. Notar que se ocupa paréntesis cuadrado\n", "\n", ":::\n", "\n", "\n" ] }, { "cell_type": "markdown", "metadata": {}, "source": [ "**Ejemplos**\n", "\n", "Consideremos el `DataFrame` anterior con información de la bolsa de Santiago\n", "\n", "Para recuperar la fila de la empresa Entel:" ] }, { "cell_type": "code", "execution_count": 4, "metadata": {}, "outputs": [ { "data": { "text/plain": [ "Monto [M$] 1408.0\n", "Variación [%] 0.0\n", "Name: ENTEL, dtype: float64" ] }, "execution_count": 4, "metadata": {}, "output_type": "execute_result" } ], "source": [ "df.loc[\"ENTEL\"]" ] }, { "cell_type": "markdown", "metadata": {}, "source": [ "Para recuperar un *slice* con las filas entre Aguas andina hasta CMPC:" ] }, { "cell_type": "code", "execution_count": 5, "metadata": {}, "outputs": [ { "data": { "text/html": [ "
\n", "\n", "\n", " \n", " \n", " \n", " \n", " \n", " \n", " \n", " \n", " \n", " \n", " \n", " \n", " \n", " \n", " \n", " \n", " \n", " \n", " \n", " \n", " \n", " \n", " \n", " \n", "
Monto [M$]Variación [%]
AGUAS-A16530.36
BSANTANDER33510.31
CMPC5998-0.60
\n", "
" ], "text/plain": [ " Monto [M$] Variación [%]\n", "AGUAS-A 1653 0.36\n", "BSANTANDER 3351 0.31\n", "CMPC 5998 -0.60" ] }, "execution_count": 5, "metadata": {}, "output_type": "execute_result" } ], "source": [ "df.loc[\"AGUAS-A\":\"CMPC\"]" ] }, { "cell_type": "markdown", "metadata": {}, "source": [ ":::{note}\n", "\n", "El operador `A:B` indica una secuencia que empieza con A y termina con B\n", "\n", ":::\n", "\n", "Para recuperar un *slice* con las filas Entel y Aguas Andina:" ] }, { "cell_type": "code", "execution_count": 6, "metadata": {}, "outputs": [ { "data": { "text/html": [ "
\n", "\n", "\n", " \n", " \n", " \n", " \n", " \n", " \n", " \n", " \n", " \n", " \n", " \n", " \n", " \n", " \n", " \n", " \n", " \n", " \n", " \n", "
Monto [M$]Variación [%]
ENTEL14080.00
AGUAS-A16530.36
\n", "
" ], "text/plain": [ " Monto [M$] Variación [%]\n", "ENTEL 1408 0.00\n", "AGUAS-A 1653 0.36" ] }, "execution_count": 6, "metadata": {}, "output_type": "execute_result" } ], "source": [ "df.loc[[\"ENTEL\", \"AGUAS-A\"]]" ] }, { "cell_type": "markdown", "metadata": {}, "source": [ ":::{note}\n", "\n", "Esto se conoce como `fancy indexing`, corresponde a indexar en base a una lista (o ndarray) que contiene los índices\n", "\n", ":::\n", "\n", "Para recuperar un *slice* con las filas anteriores pero sólo con la columna monto total:\n" ] }, { "cell_type": "code", "execution_count": 7, "metadata": {}, "outputs": [ { "data": { "text/plain": [ "ENTEL 1408\n", "AGUAS-A 1653\n", "Name: Monto [M$], dtype: int64" ] }, "execution_count": 7, "metadata": {}, "output_type": "execute_result" } ], "source": [ "df.loc[[\"ENTEL\", \"AGUAS-A\"], \"Monto [M$]\"]" ] }, { "cell_type": "markdown", "metadata": {}, "source": [ ":::{warning}\n", "\n", "Si una etiqueta está mal escrita pandas retornará un `KeyError`\n", "\n", ":::\n", "\n", ":::{hint}\n", "\n", "Si un nombre es complicado de escribir o tiene caracteres especiales podemos usar en su lugar `df.columns`, por ejemplo\n", "\n", "```python\n", "df.loc[[\"ENTEL\", \"AGUAS-A\"], df.columns[0]] \n", "```\n", "\n", "que sería equivalente a la expresión anterior\n", "\n", ":::\n", "\n", "Para recuperar la columna de monto /completa) de forma eficiente:\n", "\n", "```python\n", "df[\"Monto [M$]\"] \n", "```\n", "\n", ":::{warning}\n", "\n", "Tenga presente la siguiente \"ambigüedad\" de pandas para no confudirse:\n", "\n", "```python\n", "df[\"Monto [M$]\"] # Esto es equivalente a df.loc[:, \"Monto [M$]\"] \n", "df[\"Monto [M$]\":] # Esto retorna error\n", "df[\"BSANTANDER\"] # Esto retorna error\n", "df[\"BSANTANDER\":] # Esto es equivalente a df.loc[\"BSANTANDER\":]\n", "```\n", ":::" ] }, { "cell_type": "markdown", "metadata": {}, "source": [ "**Indexado en base a posición**\n", "\n", "Internamente, pandas asigna un número entero a cada fila y cada columna que corresponde a su posición\n", "\n", "\n", "| | 0 | 1 |\n", "|---|---|---|\n", "| 0\t| 1653 | 0.36 |\n", "| 1 |\t 3531 | -0.31 |\n", "| 2 \t| 5998 | -0.6 |\n", "| 3 | 1408 | 0.0 |\n", "\n", ":::{important}\n", "\n", "Se usa la función `iloc[]` para indexar y hacer slicing **en base a la posición**\n", "\n", ":::\n", "\n", "Por ejemplo\n", "\n", "```python\n", "df.loc[\"ENTEL\"]\n", "```\n", "y\n", "\n", "```python\n", "df.iloc[3]\n", "```\n", "son equivalentes. \n", "\n", ":::{note}\n", "\n", "`iloc[]` soporta todo lo que vimos anteriormente para `loc[]`\n", "\n", ":::" ] }, { "cell_type": "markdown", "metadata": {}, "source": [ "**Indexado rápido de un elemento**\n", "\n", "Si queremos recuperar sólo un elemento del DataFrame se recomienda usar las funciones `at[]` e `iat[]`\n", "\n", "```python\n", "df.at[\"ENTEL\", \"Monto [M$]\"]\n", "df.iat[3, 0]\n", "```\n", "\n", "Estas son mucho más rápidas que `loc[]` e `iloc[]` pero no permiten hacer *slicing* ni *fancy indexing*" ] }, { "cell_type": "markdown", "metadata": {}, "source": [ "## Combinación y manipulación de DataFrames\n", "\n", "A continuación veremos como\n", "\n", "- Combinar dos o más tablas\n", "- Intersectar dos tablas\n", "- Eliminar filas y columnas de nuestra tabla" ] }, { "cell_type": "markdown", "metadata": {}, "source": [ "Para combinar dos o más DataFrames a nivel de fila o columna se utiliza\n", "\n", "```python\n", "pd.concat(objs, # Lista de DataFrames \n", " axis=0, # Eje de concatenación 0:filas, 1: columnas\n", " join='inner', # Especifica que se hace con el eje que no se une\n", " ignore_index: bool = False, # Igual que append\n", " verify_integrity: bool = False, # Igual que append\n", "```\n", "\n", "El argumento principal es una lista de DataFrames\n", "\n", "- Podemos juntar más de dos DataFrames\n", "- Podemos juntarlos en filas o en columnas con `axis`\n", "- Podemos especificar que ocurre cuando las filas/columnas no calzan con `join`\n", "\n", ":::{note}\n", "\n", "Por defecto los valores que no se alinean al juntar dos dataframes se llenarán con NaNs\n", "\n", ":::\n", "\n", "Por ejemplo:" ] }, { "cell_type": "code", "execution_count": 8, "metadata": {}, "outputs": [ { "data": { "text/html": [ "
\n", "\n", "\n", " \n", " \n", " \n", " \n", " \n", " \n", " \n", " \n", " \n", " \n", " \n", " \n", " \n", " \n", " \n", " \n", " \n", " \n", " \n", " \n", " \n", " \n", " \n", " \n", " \n", " \n", " \n", " \n", " \n", " \n", " \n", " \n", " \n", " \n", "
Monto [M$]Variación [%]
AGUAS-A16530.36
BSANTANDER33510.31
CMPC5998-0.60
ENTEL14080.00
JabonCopito100-30.00
\n", "
" ], "text/plain": [ " Monto [M$] Variación [%]\n", "AGUAS-A 1653 0.36\n", "BSANTANDER 3351 0.31\n", "CMPC 5998 -0.60\n", "ENTEL 1408 0.00\n", "JabonCopito 100 -30.00" ] }, "execution_count": 8, "metadata": {}, "output_type": "execute_result" } ], "source": [ "df_nuevo = pd.DataFrame([[100, -30.0]], \n", " index=['JabonCopito'], \n", " columns=['Monto [M$]', 'Variación [%]'])\n", "\n", "pd.concat([df, df_nuevo], axis=0)" ] }, { "cell_type": "markdown", "metadata": {}, "source": [ "Finalmente podemos agregar una columna nueva usando el atributo `insert`\n", "\n", "```python\n", "df.insert(loc, # Ubicación de la nueva columna\n", " column, # Nombre de la nueva columna\n", " value, # Lista o ndarray con valores\n", " allow_duplicates=False # Impide agregar una columna con una etiqueta existente\n", " )\n", " \n", "```\n", "\n", "La operación `insert` es por defecto *inplace* es decir que modifica directamente a `df`" ] }, { "cell_type": "markdown", "metadata": {}, "source": [ "**Unión e intersección de DataFrames**\n", "\n", "Podemos combinar DataFrames de acuerdo a sus contenidos con la función `pandas.merge`\n", "\n", "```python\n", "pandas.merge(left, # Primer DataFrame\n", " right, # Segundo DataFrame\n", " how: str = 'inner', # Especifica como se uniran los DataFrames\n", " on=None, # Especifica la columna que se usa para combinar la DataFrames\n", " ...\n", " )\n", "```\n", "\n", "Algunos detalles:\n", "\n", "- La columna que se especifica en `on` debe existir en ambos DataFrames\n", "- El argumento `how` tiene las siguientes opciones\n", " - `left`: Se usan las llaves del primer DataFrame\n", " - `right`: Se usan las llaves del segundo DataFrame\n", " - `inner`: Se usa una intersección de las llaves de ambos DataFrames\n", " - `outer`: Se usa una unión de las llaves de ambos DataFrames\n" ] }, { "cell_type": "markdown", "metadata": {}, "source": [ "**Eliminando filas y columnas**\n", "\n", "Podemos eliminar filas o columnas de un DataFrame `df` con el atributo `drop`\n", "\n", "```python\n", "df.drop(labels=None, # Etiquetas que quiero elimina, un string o una lista\n", " axis=0, # Eliminar la etiqueta de las filas: 0 o de las columnas: 1\n", " inplace=False, # No modifica df, en su lugar retorna un DataFrame nuevo\n", " ...\n", " )\n", "```\n", "\n", "También podemos extraer y eliminar una columna usando el atributo `pop`" ] }, { "cell_type": "markdown", "metadata": {}, "source": [ "**Ordenando DataFrames**" ] }, { "cell_type": "markdown", "metadata": {}, "source": [ "Se puede ordenar un `DataFrame` según los valores de una columna usando el atributo `sort_values`\n", "\n", "```python\n", "df.sort_values(by, # Columna que guia el ordenamiento \n", " axis=0, # Se ordena según 0: filas o 1:columnas\n", " ascending=True, # Se ordenan los valores de menor a mayor\n", " inplace=False, # No se modifica df, se retorna un nuevo DataFrame\n", "```\n", "\n", "El argumento `by` recibe una etiqueta o una lista de etiquetas. En el segundo caso se ordenan jerarquicamente\n", "\n", "También existe el atributo `sort_index` que permite ordenar filas y columnas según su etiqueta" ] }, { "cell_type": "markdown", "metadata": {}, "source": [ "## Operando con DataFrames " ] }, { "cell_type": "markdown", "metadata": {}, "source": [ "**Operaciones aritméticas y lógicas**\n", "\n", "Podemos hacer operaciones aritméticas simples sobre DataFrames con los atributos \n", "\n", "- `add` y `sub`\n", "- `mul` y `div`\n", "- `pow`\n", "- y sus versiones reversas `radd`, `rdiv`, `rpow` etc\n", "\n", "También podemos hacer operaciones lógicas con los atributos\n", "\n", "- `eq` y `nq` (igual y no igual)\n", "- `lt`, `gt` (menor que y mayor que)\n", "- `le`, `ge` (menor o igual y mayor o igual\n", "\n", "Todos estos atributos tienen argumento `axis` para especificar si la operación es en fila o columna \n", "\n", "Por ejemplo\n", "\n", "```python\n", "df.add(10) # Le suma 10 a todos los valores de df\n", "df.add([0, 2]) # Le suma 0 a la primera columna y 2 a la segunda columna de df\n", "df.add(df2) # Suma los valores de df y df2 siguiendo los índices\n", "```\n", "\n", "Si usamos una lista tiene que ser tan larga como columnas tenga el `DataFrame`\n", "\n", "En general las operaciones entre columnas se guian por el índice de fila\n", "\n", "- Si un índice no es compartido se rellena con un NaN\n", "- También podemos especificar `fill_value`\n", "\n", "Si tenemos NaN en filas o columnas podemos\n", "\n", "- Eliminarlos con el atributo `dropna(axis='columns', how='any')`\n", "- Rellenarlos con el atributo `fillna(valor)`" ] }, { "cell_type": "markdown", "metadata": {}, "source": [ "**Operaciones de reducciones**\n", "\n", "Se pueden aplicar las reducciones que vimos en NumPy directamente sobre las columnas\n", "\n", "Algunos atributos para hacer reducción son\n", "\n", "- `count()`\n", "- `sum()` y `prod()`\n", "- `mean()` y `std()`\n", "- `max()` e `idxmax()`\n", "- entre otros\n", "\n", ":::{hint}\n", "\n", "También podemos usar el atributo `describe()` que nos entrega varios estadísticos de nuestras columnas\n", "\n", ":::" ] }, { "cell_type": "code", "execution_count": 9, "metadata": {}, "outputs": [ { "data": { "text/html": [ "
\n", "\n", "\n", " \n", " \n", " \n", " \n", " \n", " \n", " \n", " \n", " \n", " \n", " \n", " \n", " \n", " \n", " \n", " \n", " \n", " \n", " \n", " \n", " \n", " \n", " \n", " \n", " \n", " \n", " \n", " \n", " \n", " \n", " \n", " \n", " \n", " \n", " \n", " \n", " \n", " \n", " \n", " \n", " \n", " \n", " \n", " \n", " \n", " \n", " \n", " \n", " \n", "
Monto [M$]Variación [%]
count4.0000004.00000
mean3102.5000000.01750
std2114.8721790.44139
min1408.000000-0.60000
25%1591.750000-0.15000
50%2502.0000000.15500
75%4012.7500000.32250
max5998.0000000.36000
\n", "
" ], "text/plain": [ " Monto [M$] Variación [%]\n", "count 4.000000 4.00000\n", "mean 3102.500000 0.01750\n", "std 2114.872179 0.44139\n", "min 1408.000000 -0.60000\n", "25% 1591.750000 -0.15000\n", "50% 2502.000000 0.15500\n", "75% 4012.750000 0.32250\n", "max 5998.000000 0.36000" ] }, "execution_count": 9, "metadata": {}, "output_type": "execute_result" } ], "source": [ "df.describe()" ] }, { "cell_type": "markdown", "metadata": {}, "source": [ "**Conectando con NumPy**\n", "\n", "Podemos extraer un `ndarray` a partir de un `DataFrame` usando el atributo `values`" ] }, { "cell_type": "code", "execution_count": 10, "metadata": {}, "outputs": [ { "data": { "text/plain": [ "array([[ 1.653e+03, 3.600e-01],\n", " [ 3.351e+03, 3.100e-01],\n", " [ 5.998e+03, -6.000e-01],\n", " [ 1.408e+03, 0.000e+00]])" ] }, "execution_count": 10, "metadata": {}, "output_type": "execute_result" } ], "source": [ "df.values" ] }, { "cell_type": "markdown", "metadata": {}, "source": [ "Esto permite aplicar cualquiera de las funciones de librería NumPy\n", "\n", ":::{note}\n", "\n", "Si nuestro DataFrame tiene *strings* entonces `values` retornará un arreglo de tipo genérico *object*\n", "\n", ":::\n", "\n", "En este caso tenemos dos opciones:\n", "\n", "- Podemos usar el atributo [`select_dtypes`](https://pandas.pydata.org/pandas-docs/stable/reference/api/pandas.DataFrame.select_dtypes.html) para obtener un nuevo DataFrame con columnas de tipo numérico\n", "- Podemos convertir los atributos string a numérico (más adelante detallaremos esto)" ] }, { "cell_type": "code", "execution_count": null, "metadata": {}, "outputs": [], "source": [] } ], "metadata": { "kernelspec": { "display_name": "Python 3 (ipykernel)", "language": "python", "name": "python3" }, "language_info": { "codemirror_mode": { "name": "ipython", "version": 3 }, "file_extension": ".py", "mimetype": "text/x-python", "name": "python", "nbconvert_exporter": "python", "pygments_lexer": "ipython3", "version": "3.8.12" }, "toc": { "base_numbering": 1, "nav_menu": {}, "number_sections": true, "sideBar": true, "skip_h1_title": false, "title_cell": "Table of Contents", "title_sidebar": "Contents", "toc_cell": false, "toc_position": { "height": "calc(100% - 180px)", "left": "10px", "top": "150px", "width": "275.2px" }, "toc_section_display": true, "toc_window_display": true } }, "nbformat": 4, "nbformat_minor": 2 }