{
"cells": [
{
"cell_type": "markdown",
"metadata": {
"slideshow": {
"slide_type": "slide"
}
},
"source": [
"# Visualización Declarativa"
]
},
{
"cell_type": "markdown",
"metadata": {
"slideshow": {
"slide_type": "slide"
}
},
"source": [
"Es un paradigma de visualización en donde se busca preocuparse de los datos y sus relaciones, más que en detalles sin mayor importancia. Algunas características son:\n",
"\n",
"* Se especifica lo que se desea hacer.\n",
"* Los detalles se determinan automáticamente.\n",
"* Especificación y Ejecución están separadas.\n",
"\n",
"A modo de resumen, se refiere a construir visualizaciones a partir de los siguientes elementos:\n",
"\n",
"* _Data_\n",
"* _Transformation_\n",
"* _Marks_\n",
"* _Encoding_\n",
"* _Scale_\n",
"* _Guides_"
]
},
{
"cell_type": "markdown",
"metadata": {},
"source": [
"Para una visualización declarativa adecuata, los datos deben encontrarse en el formato _*Tidy*_, es decir:\n",
"\n",
"* Cada variable corresponde a una columna.\n",
"* Cada observación corresponde a una fila.\n",
"* Cada tipo de unidad de observación corresponde a una tabla.\n",
"\n",
"Más detalles puedes ser encontrados en el siguiente [link](http://vita.had.co.nz/papers/tidy-data.pdf)."
]
},
{
"cell_type": "markdown",
"metadata": {},
"source": [
"Un ejemplo de datos _Tidy_:"
]
},
{
"cell_type": "code",
"execution_count": 1,
"metadata": {},
"outputs": [
{
"data": {
"text/html": [
"
\n",
"\n",
"
\n",
" \n",
" \n",
" \n",
" ID \n",
" Color \n",
" Duración \n",
" \n",
" \n",
" \n",
" \n",
" 0 \n",
" 0 \n",
" Azul \n",
" 1 \n",
" \n",
" \n",
" 1 \n",
" 1 \n",
" Rojo \n",
" 1 \n",
" \n",
" \n",
" 2 \n",
" 2 \n",
" Azul \n",
" 3 \n",
" \n",
" \n",
" 3 \n",
" 3 \n",
" Azul \n",
" 3 \n",
" \n",
" \n",
" 4 \n",
" 4 \n",
" Rojo \n",
" 3 \n",
" \n",
" \n",
"
\n",
"
"
],
"text/plain": [
" ID Color Duración\n",
"0 0 Azul 1\n",
"1 1 Rojo 1\n",
"2 2 Azul 3\n",
"3 3 Azul 3\n",
"4 4 Rojo 3"
]
},
"execution_count": 1,
"metadata": {},
"output_type": "execute_result"
}
],
"source": [
"import pandas as pd\n",
"df = pd.DataFrame({\n",
" \"ID\": range(5),\n",
" \"Color\": [\"Azul\", \"Rojo\", \"Azul\", \"Azul\", \"Rojo\"],\n",
" \"Duración\": [1, 1, 3, 3, 3]\n",
" })\n",
"df.head()"
]
},
{
"cell_type": "markdown",
"metadata": {},
"source": [
"Un ejemplo de datos __NO__ _Tidy_"
]
},
{
"cell_type": "code",
"execution_count": 2,
"metadata": {},
"outputs": [
{
"data": {
"text/html": [
"\n",
"\n",
"
\n",
" \n",
" \n",
" Color \n",
" Azul \n",
" Rojo \n",
" \n",
" \n",
" ID \n",
" \n",
" \n",
" \n",
" \n",
" \n",
" \n",
" 0 \n",
" 1.0 \n",
" NaN \n",
" \n",
" \n",
" 1 \n",
" NaN \n",
" 1.0 \n",
" \n",
" \n",
" 2 \n",
" 3.0 \n",
" NaN \n",
" \n",
" \n",
" 3 \n",
" 3.0 \n",
" NaN \n",
" \n",
" \n",
" 4 \n",
" NaN \n",
" 3.0 \n",
" \n",
" \n",
"
\n",
"
"
],
"text/plain": [
"Color Azul Rojo\n",
"ID \n",
"0 1.0 NaN\n",
"1 NaN 1.0\n",
"2 3.0 NaN\n",
"3 3.0 NaN\n",
"4 NaN 3.0"
]
},
"execution_count": 2,
"metadata": {},
"output_type": "execute_result"
}
],
"source": [
"df.pivot(index=\"ID\", columns=\"Color\", values=\"Duración\")"
]
},
{
"cell_type": "markdown",
"metadata": {},
"source": [
"No es _Tidy_ puesto que la variable \"Color\" utiliza más de una columna."
]
},
{
"cell_type": "markdown",
"metadata": {},
"source": [
"__Idea:__ Buenas implementaciones pueden influir en buenas conceptualizaciones."
]
},
{
"cell_type": "markdown",
"metadata": {},
"source": [
""
]
},
{
"cell_type": "markdown",
"metadata": {},
"source": [
""
]
},
{
"cell_type": "markdown",
"metadata": {},
"source": [
"## Diferencias entre enfoques\n",
"\n",
"| Imperativa | Declarativa | \n",
"| ------|------------ | \n",
"| Especificar _cómo_ se debe hacer algo | Especificar _qué_ se quiere hacer |\n",
"| Especificación y ejecución entrelazadas | Separar especificación de ejecución |\n",
"| _Colocar un círculo rojo aquí y un círculo azul acá_ | _Mapear `x` como posición e `y` como el color_ |\n"
]
},
{
"cell_type": "markdown",
"metadata": {},
"source": [
"El _Iris dataset_ es un conjunto de datos famoso por ser un buen ejemplo, por lo que nos servirá para mostrar una de las mayores diferencias entre una visualización imperativa (como `matplotlib`) versus una declarativa (como `altair`)."
]
},
{
"cell_type": "code",
"execution_count": 3,
"metadata": {},
"outputs": [
{
"data": {
"text/plain": [
"ThemeRegistry.enable('opaque')"
]
},
"execution_count": 3,
"metadata": {},
"output_type": "execute_result"
}
],
"source": [
"import altair as alt\n",
"from vega_datasets import data # Una librería con muchos datasets\n",
"alt.themes.enable('opaque') # Para quienes utilizan temas oscuros en Jupyter Lab"
]
},
{
"cell_type": "code",
"execution_count": 4,
"metadata": {},
"outputs": [
{
"data": {
"text/plain": [
"\u001b[0;31mSignature:\u001b[0m \u001b[0mdata\u001b[0m\u001b[0;34m.\u001b[0m\u001b[0miris\u001b[0m\u001b[0;34m(\u001b[0m\u001b[0muse_local\u001b[0m\u001b[0;34m=\u001b[0m\u001b[0;32mTrue\u001b[0m\u001b[0;34m,\u001b[0m \u001b[0;34m**\u001b[0m\u001b[0mkwargs\u001b[0m\u001b[0;34m)\u001b[0m\u001b[0;34m\u001b[0m\u001b[0;34m\u001b[0m\u001b[0m\n",
"\u001b[0;31mType:\u001b[0m Dataset\n",
"\u001b[0;31mString form:\u001b[0m \n",
"\u001b[0;31mFile:\u001b[0m /opt/conda/lib/python3.8/site-packages/vega_datasets/core.py\n",
"\u001b[0;31mDocstring:\u001b[0m \n",
"Loader for the iris dataset.\n",
"\n",
"This classic dataset contains lengths and widths of petals and sepals\n",
"for 150 iris flowers, drawn from three species. It was introduced\n",
"by R.A. Fisher in 1936 [1]_.\n",
"\n",
"This dataset is bundled with vega_datasets; it can be loaded without web access.\n",
"Dataset source: https://vega.github.io/vega-datasets/data/iris.json\n",
"\n",
"Usage\n",
"-----\n",
"\n",
" >>> from vega_datasets import data\n",
" >>> iris = data.iris()\n",
" >>> type(iris)\n",
" \n",
"\n",
"Equivalently, you can use\n",
"\n",
" >>> iris = data('iris')\n",
"\n",
"To get the raw dataset rather than the dataframe, use\n",
"\n",
" >>> data_bytes = data.iris.raw()\n",
" >>> type(data_bytes)\n",
" bytes\n",
"\n",
"To find the dataset url, use\n",
"\n",
" >>> data.iris.url\n",
" 'https://vega.github.io/vega-datasets/data/iris.json'\n",
"\n",
"Attributes\n",
"----------\n",
"filename : string\n",
" The filename in which the dataset is stored\n",
"url : string\n",
" The full URL of the dataset at http://vega.github.io\n",
"format : string\n",
" The format of the dataset: usually one of {'csv', 'tsv', 'json'}\n",
"pkg_filename : string\n",
" The path to the local dataset within the vega_datasets package\n",
"is_local : bool\n",
" True if the dataset is available locally in the package\n",
"filepath : string\n",
" If is_local is True, the local file path to the dataset.\n",
"\n",
"References\n",
"----------\n",
".. [1] R. A. Fisher (1936). 'The use of multiple measurements in\n",
" taxonomic problems'. Annals of Eugenics. 7 (2): 179-188.\n",
"\u001b[0;31mClass docstring:\u001b[0m Class to load a particular dataset by name\n",
"\u001b[0;31mCall docstring:\u001b[0m \n",
"Load and parse the dataset from remote URL or local file\n",
"\n",
"Parameters\n",
"----------\n",
"use_local : boolean\n",
" If True (default), then attempt to load the dataset locally. If\n",
" False or if the dataset is not available locally, then load the\n",
" data from an external URL.\n",
"**kwargs :\n",
" additional keyword arguments are passed to data parser (usually\n",
" pd.read_csv or pd.read_json, depending on the format of the data\n",
" source)\n",
"\n",
"Returns\n",
"-------\n",
"data :\n",
" parsed data\n"
]
},
"metadata": {},
"output_type": "display_data"
}
],
"source": [
"# Una breve descripción\n",
"data.iris?"
]
},
{
"cell_type": "code",
"execution_count": 5,
"metadata": {},
"outputs": [
{
"data": {
"text/html": [
"\n",
"\n",
"
\n",
" \n",
" \n",
" \n",
" sepalLength \n",
" sepalWidth \n",
" petalLength \n",
" petalWidth \n",
" species \n",
" \n",
" \n",
" \n",
" \n",
" 0 \n",
" 5.1 \n",
" 3.5 \n",
" 1.4 \n",
" 0.2 \n",
" setosa \n",
" \n",
" \n",
" 1 \n",
" 4.9 \n",
" 3.0 \n",
" 1.4 \n",
" 0.2 \n",
" setosa \n",
" \n",
" \n",
" 2 \n",
" 4.7 \n",
" 3.2 \n",
" 1.3 \n",
" 0.2 \n",
" setosa \n",
" \n",
" \n",
" 3 \n",
" 4.6 \n",
" 3.1 \n",
" 1.5 \n",
" 0.2 \n",
" setosa \n",
" \n",
" \n",
" 4 \n",
" 5.0 \n",
" 3.6 \n",
" 1.4 \n",
" 0.2 \n",
" setosa \n",
" \n",
" \n",
"
\n",
"
"
],
"text/plain": [
" sepalLength sepalWidth petalLength petalWidth species\n",
"0 5.1 3.5 1.4 0.2 setosa\n",
"1 4.9 3.0 1.4 0.2 setosa\n",
"2 4.7 3.2 1.3 0.2 setosa\n",
"3 4.6 3.1 1.5 0.2 setosa\n",
"4 5.0 3.6 1.4 0.2 setosa"
]
},
"execution_count": 5,
"metadata": {},
"output_type": "execute_result"
}
],
"source": [
"iris = data.iris()\n",
"iris.head()"
]
},
{
"cell_type": "code",
"execution_count": 6,
"metadata": {},
"outputs": [
{
"name": "stdout",
"output_type": "stream",
"text": [
"\n",
"RangeIndex: 150 entries, 0 to 149\n",
"Data columns (total 5 columns):\n",
" # Column Non-Null Count Dtype \n",
"--- ------ -------------- ----- \n",
" 0 sepalLength 150 non-null float64\n",
" 1 sepalWidth 150 non-null float64\n",
" 2 petalLength 150 non-null float64\n",
" 3 petalWidth 150 non-null float64\n",
" 4 species 150 non-null object \n",
"dtypes: float64(4), object(1)\n",
"memory usage: 6.0+ KB\n"
]
}
],
"source": [
"iris.info()"
]
},
{
"cell_type": "code",
"execution_count": 7,
"metadata": {},
"outputs": [
{
"data": {
"text/html": [
"\n",
"\n",
"
\n",
" \n",
" \n",
" \n",
" sepalLength \n",
" sepalWidth \n",
" petalLength \n",
" petalWidth \n",
" species \n",
" \n",
" \n",
" \n",
" \n",
" count \n",
" 150.000000 \n",
" 150.000000 \n",
" 150.000000 \n",
" 150.000000 \n",
" 150 \n",
" \n",
" \n",
" unique \n",
" NaN \n",
" NaN \n",
" NaN \n",
" NaN \n",
" 3 \n",
" \n",
" \n",
" top \n",
" NaN \n",
" NaN \n",
" NaN \n",
" NaN \n",
" virginica \n",
" \n",
" \n",
" freq \n",
" NaN \n",
" NaN \n",
" NaN \n",
" NaN \n",
" 50 \n",
" \n",
" \n",
" mean \n",
" 5.843333 \n",
" 3.057333 \n",
" 3.758000 \n",
" 1.199333 \n",
" NaN \n",
" \n",
" \n",
" std \n",
" 0.828066 \n",
" 0.435866 \n",
" 1.765298 \n",
" 0.762238 \n",
" NaN \n",
" \n",
" \n",
" min \n",
" 4.300000 \n",
" 2.000000 \n",
" 1.000000 \n",
" 0.100000 \n",
" NaN \n",
" \n",
" \n",
" 25% \n",
" 5.100000 \n",
" 2.800000 \n",
" 1.600000 \n",
" 0.300000 \n",
" NaN \n",
" \n",
" \n",
" 50% \n",
" 5.800000 \n",
" 3.000000 \n",
" 4.350000 \n",
" 1.300000 \n",
" NaN \n",
" \n",
" \n",
" 75% \n",
" 6.400000 \n",
" 3.300000 \n",
" 5.100000 \n",
" 1.800000 \n",
" NaN \n",
" \n",
" \n",
" max \n",
" 7.900000 \n",
" 4.400000 \n",
" 6.900000 \n",
" 2.500000 \n",
" NaN \n",
" \n",
" \n",
"
\n",
"
"
],
"text/plain": [
" sepalLength sepalWidth petalLength petalWidth species\n",
"count 150.000000 150.000000 150.000000 150.000000 150\n",
"unique NaN NaN NaN NaN 3\n",
"top NaN NaN NaN NaN virginica\n",
"freq NaN NaN NaN NaN 50\n",
"mean 5.843333 3.057333 3.758000 1.199333 NaN\n",
"std 0.828066 0.435866 1.765298 0.762238 NaN\n",
"min 4.300000 2.000000 1.000000 0.100000 NaN\n",
"25% 5.100000 2.800000 1.600000 0.300000 NaN\n",
"50% 5.800000 3.000000 4.350000 1.300000 NaN\n",
"75% 6.400000 3.300000 5.100000 1.800000 NaN\n",
"max 7.900000 4.400000 6.900000 2.500000 NaN"
]
},
"execution_count": 7,
"metadata": {},
"output_type": "execute_result"
}
],
"source": [
"iris.describe(include=\"all\")"
]
},
{
"cell_type": "markdown",
"metadata": {},
"source": [
"El ejemplo clásico consiste en graficar _sepalWidth_ versus _petalLength_ y colorear por especie. "
]
},
{
"cell_type": "markdown",
"metadata": {},
"source": [
"### Imperativo\n",
"\n",
"En `matplotlib` sería algo así:"
]
},
{
"cell_type": "code",
"execution_count": 8,
"metadata": {},
"outputs": [],
"source": [
"import matplotlib.pyplot as plt"
]
},
{
"cell_type": "code",
"execution_count": 9,
"metadata": {},
"outputs": [
{
"data": {
"image/png": "iVBORw0KGgoAAAANSUhEUgAAAmEAAAF1CAYAAACgWj1bAAAAOXRFWHRTb2Z0d2FyZQBNYXRwbG90bGliIHZlcnNpb24zLjMuMiwgaHR0cHM6Ly9tYXRwbG90bGliLm9yZy8vihELAAAACXBIWXMAAAsTAAALEwEAmpwYAAA25UlEQVR4nO3dfXhU9ZnH/88NBHkIkDSmFBIB22tV5EHRBB/q1qrVWmFLt6Y/6dZWqPuzLkq1XVrX3Vbcutv+2mXXPmh13cVKq1XYWJstta1i67ZaH5IgT5qC1lJNohAjCQkBBXL//jgDJiFkBjJzzpmZ9+u6cs3MmTPn3HNCztycOd/zMXcXAAAAwjUk6gIAAADyEU0YAABABGjCAAAAIkATBgAAEAGaMAAAgAjQhAEAAEQg402YmQ01s+fMbHU/z33QzNrNbF3i56ZM1wMAABAHw0JYx3WSGiSNPczzv3P3uSHUAQAAEBsZbcLMrFzSHEn/KumL6Vjmscce61OmTEnHogAAADKqvr7+DXcv7e+5TB8J+7akL0saM8A8Z5nZeknNkpa4+/MDLXDKlCmqq6tLX4UAAAAZYmZ/PtxzGTsnzMzmStru7vUDzLZW0mR3P0XS9yT99DDLusrM6sysrqWlJf3FAgAAhCyTJ+a/X9JHzWyrpAcknW9m9/acwd13untn4v7DkgrM7Ni+C3L3u9y9wt0rSkv7PaIHAACQVTLWhLn7je5e7u5TJM2X9Gt3v7znPGb2HjOzxP3ZiXpaM1UTAABAXIQxOrIXM7taktz9TklVkv7OzPZJ2i1pvrt72DUBAJBv9u7dq8bGRu3ZsyfqUnLCiBEjVF5eroKCgpRfY9nW81RUVDgn5gMAMDh/+tOfNGbMGJWUlCjxpRSOkrurtbVVHR0dOv7443s9Z2b17l7R3+u4Yj4AAHloz549NGBpYmYqKSk54qOKNGEAAOQpGrD0OZptSRMGAACyziWXXKK2traoyxiU0E/MBwAAGKyHH3446hIGjSNhWayxUXroIemuu4LbxsaoKwIA4B27du3SnDlzdMopp2j69OlauXKlpkyZohtuuEGzZ8/W7Nmz9dJLL0mSWlpadOmll6qyslKVlZV68sknJUmdnZ1auHChZsyYoZkzZ+rBBx+UFCTovPHGG5Kke++9V7Nnz9app56qz33uc9q/f7/279+vBQsWaPr06ZoxY4ZuvfXWaDbCADgSlqUaG6WaGqmoSBo/XursDB7PmyeVl0ddHQAA0i9/+UtNnDhRP//5zyVJ7e3tuuGGGzR27Fg9++yz+uEPf6jrr79eq1ev1nXXXacvfOELOuecc/TKK6/owx/+sBoaGnTLLbdo3Lhx2rhxoyRpx44dvdbR0NCglStX6sknn1RBQYEWLVqk++67T9OmTVNTU5M2bdokSbH86pImLEvV1gYN2NixweMDt7W1NGEAgHiYMWOGlixZohtuuEFz587VX/7lX0qSPvnJTx68/cIXviBJWrNmjV544YWDr925c6c6Ojq0Zs0aPfDAAwenFxcX91rHY489pvr6elVWVkqSdu/erXe/+936q7/6K7388stavHix5syZo4suuiij7/Vo0IRlqZaW4AhYT4WF0rZt0dQDAEBfJ5xwgurr6/Xwww/rxhtvPNgI9RxJeOB+d3e3nnrqKY0cObLXMtx9wJGH7q4rrrhC3/jGNw55bv369frVr36l22+/XatWrdLdd9+djreVNpwTlqVKS4OvIHvq7AymAwAQB83NzRo1apQuv/xyLVmyRGvXrpUkrVy58uDtWWedJUm66KKLdNtttx187bp16/qd3vfryAsuuEDV1dXavn27JOnNN9/Un//8Z73xxhvq7u7WpZdeqltuueXguuOEI2FZqrIyOAdMCo6AdXZKbW3SuedGWhYAAAdt3LhRX/rSlzRkyBAVFBTojjvuUFVVld566y2dccYZ6u7u1v333y9J+u53v6trrrlGM2fO1L59+/SBD3xAd955p77yla/ommuu0fTp0zV06FAtXbpUH//4xw+u4+STT9a//Mu/6KKLLlJ3d7cKCgp0++23a+TIkVq4cKG6u7slqd8jZVEjtiiLNTYG54C1tARHwCorOR8MAJCahoYGTZ06NfT1TpkyRXV1dTr22GNDX3em9bdNB4ot4khYFisvp+kCACBb0YQBAIDQbN26NeoSYoMT8wEAACJAEwYAABABmjAAAIAI0IQBAABEgCYMAADE3j333KPm5uaoy0grmjAAABB7NGEAACAvNTZKDz0k3XVXcNvYOPhl7tq1S3PmzNEpp5yi6dOna+XKlaqvr9e5556r008/XR/+8If12muvqbq6WnV1dfrUpz6lU089Vbt379Zjjz2mWbNmacaMGfrsZz+rt956S5L0D//wDzr55JM1c+ZMLVmyRJL0s5/9TGeccYZmzZqlD33oQ9oWk6BlmjAAADCgxsYgKq+rSxo/PritqRl8I/bLX/5SEydO1Pr167Vp0yZdfPHFWrx4saqrq1VfX6/Pfvaz+qd/+idVVVWpoqJC9913n9atWycz04IFC7Ry5Upt3LhR+/bt0x133KE333xTDz30kJ5//nlt2LBBX/nKVyRJ55xzjp5++mk999xzmj9/vr71rW+lYasMHhdrBQAAA6qtlYqKpLFjg8cHbmtrB5fcMmPGDC1ZskQ33HCD5s6dq+LiYm3atEkXXnihJGn//v2aMGHCIa/bvHmzjj/+eJ1wwgmSpCuuuEK33367rr32Wo0YMUJ/+7d/qzlz5mju3LmSpMbGRl122WV67bXX9Pbbb+v4448/+qLTiCNhAABgQC0tUmFh72mFhcH0wTjhhBNUX1+vGTNm6MYbb9SDDz6oadOmad26dVq3bp02btyoRx555JDXHS73etiwYXr22Wd16aWX6qc//akuvvhiSdLixYt17bXXauPGjfrP//xP7dmzZ3CFpwlNGAAAGFBpqdTZ2XtaZ2cwfTCam5s1atQoXX755VqyZImeeeYZtbS06KmnnpIk7d27V88//7wkacyYMero6JAknXTSSdq6dateeuklSdKPfvQjnXvuuers7FR7e7suueQSffvb39a6deskSe3t7SorK5MkrVixYnBFpxFfRwIAgAFVVgbngEnBEbDOTqmtTTr33MEtd+PGjfrSl76kIUOGqKCgQHfccYeGDRumz3/+82pvb9e+fft0/fXXa9q0aVqwYIGuvvpqjRw5Uk899ZR+8IMf6BOf+IT27dunyspKXX311XrzzTc1b9487dmzR+6uW2+9VZJ088036xOf+ITKysp05pln6k9/+tPgCk8TO9whvbiqqKjwurq6qMsAACCrNTQ0aOrUqSnP39gYnAPW0hIcAausHNz5YLmov21qZvXuXtHf/BwJAwAASZWX03SlG+eEAQAARIAmDAAAIAJ8HRlTfPcOAEBu40hYDGXqysQAACA+aMJiqOeViYcMCW6LioLpAAAgN9CExVCmrkwMAECuu+mmm7RmzZojft3jjz9+MOYoLJwTFkMHrkx8IJtLSs+ViQEAyAXuLnfXkCGHHkv62te+FkoN+/bt07Bhg2ujOBIWQ5WVwZWId+6UuruD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\n",
"text/plain": [
""
]
},
"metadata": {
"needs_background": "light"
},
"output_type": "display_data"
}
],
"source": [
"color_map = dict(zip(iris[\"species\"].unique(), \n",
" [\"blue\", \"green\", \"red\"]))\n",
"\n",
"plt.figure(figsize=(10, 6))\n",
"\n",
"for species, group in iris.groupby(\"species\"):\n",
" plt.scatter(group[\"petalLength\"], \n",
" group[\"sepalWidth\"],\n",
" color=color_map[species],\n",
" alpha=0.3,\n",
" edgecolor=None,\n",
" label=species,\n",
" )\n",
" \n",
"plt.legend(frameon=True, title=\"species\")\n",
"plt.xlabel(\"petalLength\")\n",
"plt.ylabel(\"sepalWidth\")\n",
"plt.show()"
]
},
{
"cell_type": "markdown",
"metadata": {},
"source": [
"### Declarativo\n",
"\n",
"En `altair` sería algo así:"
]
},
{
"cell_type": "code",
"execution_count": 10,
"metadata": {},
"outputs": [
{
"data": {
"text/html": [
"\n",
"
\n",
""
],
"text/plain": [
"alt.Chart(...)"
]
},
"execution_count": 10,
"metadata": {},
"output_type": "execute_result"
}
],
"source": [
"alt.Chart(iris).mark_point().encode(\n",
" x=\"petalLength\",\n",
" y=\"sepalWidth\",\n",
" color=\"species\"\n",
")"
]
},
{
"cell_type": "markdown",
"metadata": {},
"source": [
"Spoiler: Solo bastan un par de líneas extras para crear un gráfico interactivo!"
]
},
{
"cell_type": "code",
"execution_count": 11,
"metadata": {},
"outputs": [
{
"data": {
"text/html": [
"\n",
"
\n",
""
],
"text/plain": [
"alt.Chart(...)"
]
},
"execution_count": 11,
"metadata": {},
"output_type": "execute_result"
}
],
"source": [
"alt.Chart(iris).mark_point().encode(\n",
" x=\"petalLength\",\n",
" y=\"sepalWidth\",\n",
" color=\"species\",\n",
" tooltip=\"species\"\n",
").interactive()"
]
},
{
"cell_type": "markdown",
"metadata": {},
"source": [
"## Altair \n",
"\n",
"_Altair is a declarative statistical visualization library for Python, based on Vega and Vega-Lite, and the source is available on GitHub._\n",
"\n",
"_With Altair, you can spend more time understanding your data and its meaning. Altair’s API is simple, friendly and consistent and built on top of the powerful Vega-Lite visualization grammar. This elegant simplicity produces beautiful and effective visualizations with a minimal amount of code._"
]
},
{
"cell_type": "markdown",
"metadata": {},
"source": [
"### Data\n",
"Los datos en Altair son basados en Dataframe de Pandas, los cuales deben ser _Tidy_ para una mejor experiencia.\n",
"\n",
"El objeto _*Chart*_ es el fundamental, pues tiene como argumento los datos."
]
},
{
"cell_type": "code",
"execution_count": 12,
"metadata": {},
"outputs": [],
"source": [
"# Utilizaremos estos datos como ejemplo\n",
"import pandas as pd\n",
"df = pd.DataFrame({'a': list('CCCDDDEEE'),\n",
" 'b': [2, 7, 4, 1, 2, 6, 8, 4, 7]})"
]
},
{
"cell_type": "code",
"execution_count": 13,
"metadata": {},
"outputs": [],
"source": [
"import altair as alt\n",
"chart = alt.Chart(df)"
]
},
{
"cell_type": "markdown",
"metadata": {},
"source": [
"### Mark\n",
"\n",
"¿Cómo queremos que se vean los datos? La respuesta está en los _marks_, que en Altair corresponden a un método de un objeto _Chart_. "
]
},
{
"cell_type": "code",
"execution_count": 14,
"metadata": {},
"outputs": [
{
"data": {
"text/html": [
"\n",
"
\n",
""
],
"text/plain": [
"alt.Chart(...)"
]
},
"execution_count": 14,
"metadata": {},
"output_type": "execute_result"
}
],
"source": [
"alt.Chart(df).mark_point()"
]
},
{
"cell_type": "markdown",
"metadata": {},
"source": [
"La representación anterior consiste en un solo punto, pues aún no se ha especificado las posiciones de los puntos."
]
},
{
"cell_type": "markdown",
"metadata": {},
"source": [
"### Encoding\n",
"\n",
"Canales asociados a columnas de los datos con tal de separar visualmente los elementos (que para estos datos se están graficando puntos). \n",
"\n",
"Por ejemplo, es posible codificar la variable `a` con el canal `x`, que representa el eje horizontal donde se posicionan los puntos. Esto es posible mediante el método `encode` de los objetos _Charts_."
]
},
{
"cell_type": "code",
"execution_count": 15,
"metadata": {},
"outputs": [
{
"data": {
"text/html": [
"\n",
"
\n",
""
],
"text/plain": [
"alt.Chart(...)"
]
},
"execution_count": 15,
"metadata": {},
"output_type": "execute_result"
}
],
"source": [
"alt.Chart(df).mark_point().encode(\n",
" x='a',\n",
")"
]
},
{
"cell_type": "markdown",
"metadata": {},
"source": [
"Los principales canales de _encoding_ son `x`, `y`, `color`, `shape`, `size`, etc. los cuales se pueden designar utilizando el nombre de la columna asociada a los datos."
]
},
{
"cell_type": "markdown",
"metadata": {},
"source": [
"Finalmente, separemos la posición vertical asignando el canal `y`, que como te imaginas, corresponde al eje vertical."
]
},
{
"cell_type": "code",
"execution_count": 16,
"metadata": {},
"outputs": [
{
"data": {
"text/html": [
"\n",
"
\n",
""
],
"text/plain": [
"alt.Chart(...)"
]
},
"execution_count": 16,
"metadata": {},
"output_type": "execute_result"
}
],
"source": [
"alt.Chart(df).mark_point().encode(\n",
" x='a',\n",
" y='b'\n",
")"
]
},
{
"cell_type": "markdown",
"metadata": {},
"source": [
"### Transformación\n",
"\n",
"Altair permite incluso transformar datos con tal de entregar mayor flexibilidad, para ello dispone de una sintaxis incorporada para `Agregaciones`. Por ejemplo, para calcular el promedio."
]
},
{
"cell_type": "code",
"execution_count": 17,
"metadata": {},
"outputs": [
{
"data": {
"text/html": [
"\n",
"
\n",
""
],
"text/plain": [
"alt.Chart(...)"
]
},
"execution_count": 17,
"metadata": {},
"output_type": "execute_result"
}
],
"source": [
"alt.Chart(df).mark_point().encode(\n",
" x='a',\n",
" y='average(b)'\n",
")"
]
},
{
"cell_type": "markdown",
"metadata": {},
"source": [
"Aunque en realidad es más acertado utilizar gráficos de barra para mostrar agregaciones. Es tan fácil como cambiar el método `mark_point()` por `mark_bar()`."
]
},
{
"cell_type": "code",
"execution_count": 18,
"metadata": {},
"outputs": [
{
"data": {
"text/html": [
"\n",
"
\n",
""
],
"text/plain": [
"alt.Chart(...)"
]
},
"execution_count": 18,
"metadata": {},
"output_type": "execute_result"
}
],
"source": [
"alt.Chart(df).mark_bar().encode(\n",
" x='a',\n",
" y='average(b)'\n",
")"
]
},
{
"cell_type": "markdown",
"metadata": {},
"source": [
"### Personalización\n",
"\n",
"Por defecto, Altair a través de Vega-Lite realiza algunas elecciones sobre las propiedades por defecto en cada visualización. Sin embargo, Altair también provee una API para personalizar los gráficos. Por ejemplo, es posible especificar el título de cada eje utilizando los atributos de los canales. Inclusive es posible escoger el color de los _marks_."
]
},
{
"cell_type": "code",
"execution_count": 19,
"metadata": {},
"outputs": [
{
"data": {
"text/html": [
"\n",
"
\n",
""
],
"text/plain": [
"alt.Chart(...)"
]
},
"execution_count": 19,
"metadata": {},
"output_type": "execute_result"
}
],
"source": [
"alt.Chart(df).mark_bar(color='firebrick').encode(\n",
" y=alt.Y('a', axis=alt.Axis(title='category')),\n",
" x=alt.X('average(b)', axis=alt.Axis(title='avg(b) by category'))\n",
")"
]
},
{
"cell_type": "markdown",
"metadata": {},
"source": [
"En el ejemplo anterior no basta con solo el nombre de la columna, es necesario crear el objeto `alt.__` correspondiente a los canales."
]
},
{
"cell_type": "markdown",
"metadata": {},
"source": [
"Otro ejemplo útil consiste en juntar dos gŕaficos en una misma figura."
]
},
{
"cell_type": "code",
"execution_count": 20,
"metadata": {},
"outputs": [
{
"data": {
"text/html": [
"\n",
"
\n",
""
],
"text/plain": [
"alt.HConcatChart(...)"
]
},
"execution_count": 20,
"metadata": {},
"output_type": "execute_result"
}
],
"source": [
"vertical_chart = alt.Chart(df).mark_bar().encode(\n",
" x='a',\n",
" y='average(b)'\n",
")\n",
"\n",
"horizontal_chart = alt.Chart(df).mark_bar(color='firebrick').encode(\n",
" y=alt.Y('a', axis=alt.Axis(title='category')),\n",
" x=alt.X('average(b)', axis=alt.Axis(title='avg(b) by category'))\n",
")\n",
"\n",
"vertical_chart | horizontal_chart "
]
},
{
"cell_type": "markdown",
"metadata": {},
"source": [
"Inclusive se puden sumar!"
]
},
{
"cell_type": "code",
"execution_count": 21,
"metadata": {},
"outputs": [
{
"data": {
"text/html": [
"\n",
"
\n",
""
],
"text/plain": [
"alt.LayerChart(...)"
]
},
"execution_count": 21,
"metadata": {},
"output_type": "execute_result"
}
],
"source": [
"vertical_chart_point = alt.Chart(df).mark_point(color='firebrick').encode(\n",
" x='a',\n",
" y='average(b)'\n",
")\n",
"\n",
"vertical_chart + vertical_chart_point"
]
},
{
"cell_type": "markdown",
"metadata": {},
"source": [
"## Gráfico a Gráfico"
]
},
{
"cell_type": "markdown",
"metadata": {},
"source": [
"### Gráfico de Barras"
]
},
{
"cell_type": "code",
"execution_count": 22,
"metadata": {},
"outputs": [
{
"data": {
"text/html": [
"\n",
"
\n",
""
],
"text/plain": [
"alt.Chart(...)"
]
},
"execution_count": 22,
"metadata": {},
"output_type": "execute_result"
}
],
"source": [
"source = pd.DataFrame({\n",
" 'a': ['A', 'B', 'C', 'D', 'E', 'F', 'G', 'H', 'I'],\n",
" 'b': [28, 55, 43, 91, 81, 53, 19, 87, 52]\n",
"})\n",
"\n",
"alt.Chart(source).mark_bar().encode(\n",
" x='a',\n",
" y='b'\n",
")"
]
},
{
"cell_type": "markdown",
"metadata": {},
"source": [
"Añadir una capa de complejidad, ya sea diferenciar por color, es tan simple como:"
]
},
{
"cell_type": "code",
"execution_count": 23,
"metadata": {},
"outputs": [
{
"data": {
"text/html": [
"\n",
"
\n",
""
],
"text/plain": [
"alt.Chart(...)"
]
},
"execution_count": 23,
"metadata": {},
"output_type": "execute_result"
}
],
"source": [
"source = data.barley() # Datos de cultivos\n",
"\n",
"alt.Chart(source).mark_bar().encode(\n",
" x='year:O',\n",
" y='sum(yield):Q',\n",
" color='year:N',\n",
" column='site:N'\n",
")"
]
},
{
"cell_type": "markdown",
"metadata": {},
"source": [
"### Histogramas"
]
},
{
"cell_type": "markdown",
"metadata": {},
"source": [
"Este ejemplo muesrta un histograma con una línea superpuesta indicando la media global."
]
},
{
"cell_type": "code",
"execution_count": 24,
"metadata": {},
"outputs": [
{
"data": {
"text/html": [
"\n",
"
\n",
""
],
"text/plain": [
"alt.LayerChart(...)"
]
},
"execution_count": 24,
"metadata": {},
"output_type": "execute_result"
}
],
"source": [
"source = data.cars()\n",
"\n",
"base = alt.Chart(source)\n",
"\n",
"hist = base.mark_bar().encode(\n",
" x=alt.X('Horsepower:Q', bin=True),\n",
" y='count()'\n",
")\n",
"\n",
"rule = base.mark_rule(color='red').encode(\n",
" x='mean(Horsepower):Q',\n",
" size=alt.value(5)\n",
")\n",
"\n",
"hist + rule\n"
]
},
{
"cell_type": "markdown",
"metadata": {},
"source": [
"### Gráfico de Líneas\n"
]
},
{
"cell_type": "code",
"execution_count": 25,
"metadata": {},
"outputs": [
{
"data": {
"text/html": [
"\n",
"
\n",
""
],
"text/plain": [
"alt.Chart(...)"
]
},
"execution_count": 25,
"metadata": {},
"output_type": "execute_result"
}
],
"source": [
"import numpy as np\n",
"x = np.arange(100)\n",
"source = pd.DataFrame({\n",
" 'x': x,\n",
" 'f(x)': np.sin(x / 5)\n",
"})\n",
"\n",
"alt.Chart(source).mark_line().encode(\n",
" x='x',\n",
" y='f(x)'\n",
")"
]
},
{
"cell_type": "markdown",
"metadata": {},
"source": [
"Este ejemplo muestra un gráfico de líneas de series múltiples de los precios de cierre diarios de las acciones de AAPL, AMZN, GOOG, IBM y MSFT entre 2000 y 2010."
]
},
{
"cell_type": "code",
"execution_count": 26,
"metadata": {},
"outputs": [
{
"data": {
"text/html": [
"\n",
"
\n",
""
],
"text/plain": [
"alt.Chart(...)"
]
},
"execution_count": 26,
"metadata": {},
"output_type": "execute_result"
}
],
"source": [
"source = data.stocks()\n",
"\n",
"alt.Chart(source).mark_line().encode(\n",
" x='date',\n",
" y='price',\n",
" color='symbol'\n",
")"
]
},
{
"cell_type": "markdown",
"metadata": {},
"source": [
"### Scatter Plot\n"
]
},
{
"cell_type": "code",
"execution_count": 27,
"metadata": {},
"outputs": [
{
"data": {
"text/html": [
"\n",
"
\n",
""
],
"text/plain": [
"alt.Chart(...)"
]
},
"execution_count": 27,
"metadata": {},
"output_type": "execute_result"
}
],
"source": [
"source = data.cars()\n",
"\n",
"alt.Chart(source).mark_circle(size=60).encode(\n",
" x='Horsepower',\n",
" y='Miles_per_Gallon',\n",
" color='Origin',\n",
")"
]
},
{
"cell_type": "markdown",
"metadata": {},
"source": [
"Un scatter plot con una superposición media continua. En este ejemplo, se utiliza una ventana de 30 días para calcular la media de la temperatura máxima alrededor de cada fecha."
]
},
{
"cell_type": "code",
"execution_count": 28,
"metadata": {},
"outputs": [
{
"data": {
"text/html": [
"\n",
"
\n",
""
],
"text/plain": [
"alt.LayerChart(...)"
]
},
"execution_count": 28,
"metadata": {},
"output_type": "execute_result"
}
],
"source": [
"source = data.seattle_weather()\n",
"\n",
"line = alt.Chart(source).mark_line(\n",
" color='red',\n",
" size=2\n",
").transform_window(\n",
" rolling_mean='mean(temp_max)',\n",
" frame=[-15, 15]\n",
").encode(\n",
" x='date:T',\n",
" y='rolling_mean:Q'\n",
")\n",
"\n",
"points = alt.Chart(source).mark_point().encode(\n",
" x='date:T',\n",
" y=alt.Y('temp_max:Q',\n",
" axis=alt.Axis(title='Max Temp'))\n",
")\n",
"\n",
"points + line"
]
},
{
"cell_type": "markdown",
"metadata": {},
"source": [
"### Gráfico de Barras de Error"
]
},
{
"cell_type": "markdown",
"metadata": {},
"source": [
"Este ejemplo muestra barras de error con desviación estándar utilizando diferentes datos de rendimiento de cultivos en la década de 1930."
]
},
{
"cell_type": "code",
"execution_count": 29,
"metadata": {},
"outputs": [
{
"data": {
"text/html": [
"\n",
"
\n",
""
],
"text/plain": [
"alt.LayerChart(...)"
]
},
"execution_count": 29,
"metadata": {},
"output_type": "execute_result"
}
],
"source": [
"source = data.barley()\n",
"\n",
"error_bars = alt.Chart(source).mark_errorbar(extent='stdev').encode(\n",
" x=alt.X('yield:Q', scale=alt.Scale(zero=False)),\n",
" y=alt.Y('variety:N')\n",
")\n",
"\n",
"points = alt.Chart(source).mark_point(filled=True, color='black').encode(\n",
" x=alt.X('yield:Q', aggregate='mean'),\n",
" y=alt.Y('variety:N'),\n",
")\n",
"\n",
"error_bars + points"
]
},
{
"cell_type": "markdown",
"metadata": {},
"source": [
"### Gráficos de Area\n"
]
},
{
"cell_type": "markdown",
"metadata": {},
"source": [
"Este ejemplo muestra como hacer un gráfico de área apilado y normalizado."
]
},
{
"cell_type": "code",
"execution_count": 30,
"metadata": {},
"outputs": [
{
"data": {
"text/html": [
"\n",
"
\n",
""
],
"text/plain": [
"alt.Chart(...)"
]
},
"execution_count": 30,
"metadata": {},
"output_type": "execute_result"
}
],
"source": [
"source = data.iowa_electricity()\n",
"\n",
"alt.Chart(source).mark_area().encode(\n",
" x=\"year:T\",\n",
" y=alt.Y(\"net_generation:Q\"),\n",
" color=\"source:N\"\n",
")"
]
},
{
"cell_type": "markdown",
"metadata": {},
"source": [
"Este ejemplo muestra como hacer un gráfico de área apilado y normalizado."
]
},
{
"cell_type": "code",
"execution_count": 31,
"metadata": {},
"outputs": [
{
"data": {
"text/html": [
"\n",
"
\n",
""
],
"text/plain": [
"alt.Chart(...)"
]
},
"execution_count": 31,
"metadata": {},
"output_type": "execute_result"
}
],
"source": [
"source = data.iowa_electricity()\n",
"\n",
"alt.Chart(source).mark_area().encode(\n",
" x=\"year:T\",\n",
" y=alt.Y(\"net_generation:Q\", stack=\"normalize\"),\n",
" color=\"source:N\"\n",
")"
]
},
{
"cell_type": "markdown",
"metadata": {},
"source": [
"### Mapas de Calor\n"
]
},
{
"cell_type": "markdown",
"metadata": {},
"source": [
"Este ejemplo muestra un mapa de calor simple para mostrar datos cuadriculados."
]
},
{
"cell_type": "code",
"execution_count": 32,
"metadata": {},
"outputs": [
{
"data": {
"text/html": [
"\n",
"
\n",
""
],
"text/plain": [
"alt.Chart(...)"
]
},
"execution_count": 32,
"metadata": {},
"output_type": "execute_result"
}
],
"source": [
"# Compute x^2 + y^2 across a 2D grid\n",
"x, y = np.meshgrid(range(-5, 5), range(-5, 5))\n",
"z = x ** 2 + y ** 2\n",
"\n",
"# Convert this grid to columnar data expected by Altair\n",
"source = pd.DataFrame({'x': x.ravel(),\n",
" 'y': y.ravel(),\n",
" 'z': z.ravel()})\n",
"\n",
"alt.Chart(source).mark_rect().encode(\n",
" x='x:O',\n",
" y='y:O',\n",
" color='z:Q'\n",
")"
]
},
{
"cell_type": "markdown",
"metadata": {},
"source": [
"Este ejemplo muestra un gráfico de texto en capas sobre un mapa de calor utilizando el conjunto de datos de automóviles."
]
},
{
"cell_type": "code",
"execution_count": 33,
"metadata": {},
"outputs": [
{
"data": {
"text/html": [
"\n",
"
\n",
""
],
"text/plain": [
"alt.LayerChart(...)"
]
},
"execution_count": 33,
"metadata": {},
"output_type": "execute_result"
}
],
"source": [
"source = data.cars()\n",
"\n",
"# Configure common options\n",
"base = alt.Chart(source).transform_aggregate(\n",
" num_cars='count()',\n",
" groupby=['Origin', 'Cylinders']\n",
").encode(\n",
" alt.X('Cylinders:O', scale=alt.Scale(paddingInner=0)),\n",
" alt.Y('Origin:O', scale=alt.Scale(paddingInner=0)),\n",
").properties(\n",
" width=400,\n",
" height=400\n",
")\n",
"\n",
"# Configure heatmap\n",
"heatmap = base.mark_rect().encode(\n",
" color=alt.Color('num_cars:Q',\n",
" scale=alt.Scale(scheme='viridis'),\n",
" legend=alt.Legend(direction='horizontal')\n",
" )\n",
")\n",
"\n",
"# Configure text\n",
"text = base.mark_text(baseline='middle').encode(\n",
" text='num_cars:Q',\n",
" color=alt.condition(\n",
" alt.datum.num_cars > 100,\n",
" alt.value('black'),\n",
" alt.value('white')\n",
" )\n",
")\n",
"\n",
"# Draw the chart\n",
"heatmap + text"
]
},
{
"cell_type": "markdown",
"metadata": {},
"source": [
"### Mapas\n"
]
},
{
"cell_type": "markdown",
"metadata": {},
"source": [
"Este ejemplo muestra un mapa coroplético de la tasa de desempleo por condado en los EE. UU."
]
},
{
"cell_type": "code",
"execution_count": 34,
"metadata": {},
"outputs": [
{
"data": {
"text/html": [
"\n",
"
\n",
""
],
"text/plain": [
"alt.Chart(...)"
]
},
"execution_count": 34,
"metadata": {},
"output_type": "execute_result"
}
],
"source": [
"counties = alt.topo_feature(data.us_10m.url, 'counties')\n",
"source = data.unemployment.url\n",
"\n",
"alt.Chart(counties).mark_geoshape().encode(\n",
" color='rate:Q'\n",
").transform_lookup(\n",
" lookup='id',\n",
" from_=alt.LookupData(source, 'id', ['rate'])\n",
").project(\n",
" type='albersUsa'\n",
").properties(\n",
" width=500,\n",
" height=300\n",
")"
]
},
{
"cell_type": "markdown",
"metadata": {},
"source": [
"Este ejemplo muestra una visualización geográfica en capas que muestra las posiciones de los aeropuertos de EE. UU. En un contexto de estados de EE. UU."
]
},
{
"cell_type": "code",
"execution_count": 35,
"metadata": {},
"outputs": [
{
"data": {
"text/html": [
"\n",
"
\n",
""
],
"text/plain": [
"alt.LayerChart(...)"
]
},
"execution_count": 35,
"metadata": {},
"output_type": "execute_result"
}
],
"source": [
"airports = data.airports.url\n",
"states = alt.topo_feature(data.us_10m.url, feature='states')\n",
"\n",
"# US states background\n",
"background = alt.Chart(states).mark_geoshape(\n",
" fill='lightgray',\n",
" stroke='white'\n",
").properties(\n",
" width=500,\n",
" height=300\n",
").project('albersUsa')\n",
"\n",
"# airport positions on background\n",
"points = alt.Chart(airports).transform_aggregate(\n",
" latitude='mean(latitude)',\n",
" longitude='mean(longitude)',\n",
" count='count()',\n",
" groupby=['state']\n",
").mark_circle().encode(\n",
" longitude='longitude:Q',\n",
" latitude='latitude:Q',\n",
" size=alt.Size('count:Q', title='Number of Airports'),\n",
" color=alt.value('steelblue'),\n",
" tooltip=['state:N','count:Q']\n",
").properties(\n",
" title='Number of airports in US'\n",
")\n",
"\n",
"background + points"
]
},
{
"cell_type": "markdown",
"metadata": {},
"source": [
"### El límite es la imaginación??\n",
"\n",
"Todos los ejemplos anteriores fueron recopilados de la Galería de Ejemplos de Altair ([link](https://altair-viz.github.io/gallery/index.html)), como podrás darte cuenta, son muchos menos que los ofrecidos por matplotlib. Altair es una librería nueva, alrededor de 3 años, versus los 17 años de matplotlib. \n",
"\n",
"Si bien crear gráficos \"comunes\" es mucho menos verboso, al querer realizar gráficos de bajo nivel matplotlib es el claro ganador. Dependiendo de tus necesidades del momento, debes saber escoger una u otra librería, __son complementarias y no enemigas!__\n",
"\n",
"Sin embargo, eso no quita el hecho que en Altair se puedan realizar gráficos \"poco convencionales\"."
]
},
{
"cell_type": "code",
"execution_count": 36,
"metadata": {},
"outputs": [
{
"data": {
"text/html": [
"\n",
"
\n",
""
],
"text/plain": [
"alt.Chart(...)"
]
},
"execution_count": 36,
"metadata": {},
"output_type": "execute_result"
}
],
"source": [
"\"\"\"\n",
"Isotype Visualization shows the distribution of animals across UK and US, \n",
"using unicode emoji marks rather than custom SVG paths \n",
"(see https://altair-viz.github.io/gallery/isotype.html). \n",
"This is adapted from Vega-Lite example https://vega.github.io/vega-lite/examples/isotype_bar_chart_emoji.html.\n",
"\"\"\"\n",
"\n",
"source = pd.DataFrame([\n",
" {'country': 'Great Britain', 'animal': 'cattle'},\n",
" {'country': 'Great Britain', 'animal': 'cattle'},\n",
" {'country': 'Great Britain', 'animal': 'cattle'},\n",
" {'country': 'Great Britain', 'animal': 'pigs'},\n",
" {'country': 'Great Britain', 'animal': 'pigs'},\n",
" {'country': 'Great Britain', 'animal': 'sheep'},\n",
" {'country': 'Great Britain', 'animal': 'sheep'},\n",
" {'country': 'Great Britain', 'animal': 'sheep'},\n",
" {'country': 'Great Britain', 'animal': 'sheep'},\n",
" {'country': 'Great Britain', 'animal': 'sheep'},\n",
" {'country': 'Great Britain', 'animal': 'sheep'},\n",
" {'country': 'Great Britain', 'animal': 'sheep'},\n",
" {'country': 'Great Britain', 'animal': 'sheep'},\n",
" {'country': 'Great Britain', 'animal': 'sheep'},\n",
" {'country': 'Great Britain', 'animal': 'sheep'},\n",
" {'country': 'United States', 'animal': 'cattle'},\n",
" {'country': 'United States', 'animal': 'cattle'},\n",
" {'country': 'United States', 'animal': 'cattle'},\n",
" {'country': 'United States', 'animal': 'cattle'},\n",
" {'country': 'United States', 'animal': 'cattle'},\n",
" {'country': 'United States', 'animal': 'cattle'},\n",
" {'country': 'United States', 'animal': 'cattle'},\n",
" {'country': 'United States', 'animal': 'cattle'},\n",
" {'country': 'United States', 'animal': 'cattle'},\n",
" {'country': 'United States', 'animal': 'pigs'},\n",
" {'country': 'United States', 'animal': 'pigs'},\n",
" {'country': 'United States', 'animal': 'pigs'},\n",
" {'country': 'United States', 'animal': 'pigs'},\n",
" {'country': 'United States', 'animal': 'pigs'},\n",
" {'country': 'United States', 'animal': 'pigs'},\n",
" {'country': 'United States', 'animal': 'sheep'},\n",
" {'country': 'United States', 'animal': 'sheep'},\n",
" {'country': 'United States', 'animal': 'sheep'},\n",
" {'country': 'United States', 'animal': 'sheep'},\n",
" {'country': 'United States', 'animal': 'sheep'},\n",
" {'country': 'United States', 'animal': 'sheep'},\n",
" {'country': 'United States', 'animal': 'sheep'}\n",
" ])\n",
"\n",
"\n",
"alt.Chart(source).mark_text(size=45, baseline='middle').encode(\n",
" alt.X('x:O', axis=None),\n",
" alt.Y('animal:O', axis=None),\n",
" alt.Row('country:N', header=alt.Header(title='')),\n",
" alt.Text('emoji:N')\n",
").transform_calculate(\n",
" emoji=\"{'cattle': '🐄', 'pigs': '🐖', 'sheep': '🐏'}[datum.animal]\"\n",
").transform_window(\n",
" x='rank()',\n",
" groupby=['country', 'animal']\n",
").properties(width=550, height=140)"
]
}
],
"metadata": {
"celltoolbar": "Slideshow",
"kernelspec": {
"display_name": "Python 3",
"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.5"
},
"toc-autonumbering": false,
"toc-showcode": false,
"toc-showmarkdowntxt": false,
"toc-showtags": true
},
"nbformat": 4,
"nbformat_minor": 4
}