{ "nbformat": 4, "nbformat_minor": 0, "metadata": { "colab": { "name": "lesson_12-R.ipynb", "provenance": [], "collapsed_sections": [] }, "kernelspec": { "name": "ir", "display_name": "R" }, "language_info": { "name": "R" }, "coopTranslator": { "original_hash": "fab50046ca413a38939d579f8432274f", "translation_date": "2025-09-04T02:36:44+00:00", "source_file": "4-Classification/3-Classifiers-2/solution/R/lesson_12-R.ipynb", "language_code": "es" } }, "cells": [ { "cell_type": "markdown", "metadata": { "id": "jsFutf_ygqSx" }, "source": [ "# Construir un modelo de clasificación: Deliciosas cocinas asiáticas e indias\n" ] }, { "cell_type": "markdown", "metadata": { "id": "HD54bEefgtNO" }, "source": [ "## Clasificadores de cocina 2\n", "\n", "En esta segunda lección sobre clasificación, exploraremos `más formas` de clasificar datos categóricos. También aprenderemos sobre las implicaciones de elegir un clasificador sobre otro.\n", "\n", "### [**Cuestionario previo a la lección**](https://gray-sand-07a10f403.1.azurestaticapps.net/quiz/23/)\n", "\n", "### **Requisito previo**\n", "\n", "Asumimos que has completado las lecciones anteriores, ya que retomaremos algunos conceptos que aprendimos antes.\n", "\n", "Para esta lección, necesitaremos los siguientes paquetes:\n", "\n", "- `tidyverse`: El [tidyverse](https://www.tidyverse.org/) es una [colección de paquetes de R](https://www.tidyverse.org/packages) diseñada para hacer la ciencia de datos más rápida, fácil y divertida.\n", "\n", "- `tidymodels`: El marco de trabajo [tidymodels](https://www.tidymodels.org/) es una [colección de paquetes](https://www.tidymodels.org/packages/) para modelado y aprendizaje automático.\n", "\n", "- `themis`: El paquete [themis](https://themis.tidymodels.org/) proporciona pasos adicionales de recetas para tratar con datos desbalanceados.\n", "\n", "Puedes instalarlos con el siguiente comando:\n", "\n", "`install.packages(c(\"tidyverse\", \"tidymodels\", \"kernlab\", \"themis\", \"ranger\", \"xgboost\", \"kknn\"))`\n", "\n", "Alternativamente, el siguiente script verifica si tienes los paquetes necesarios para completar este módulo y los instala por ti en caso de que falten.\n" ] }, { "cell_type": "code", "metadata": { "id": "vZ57IuUxgyQt" }, "source": [ "suppressWarnings(if (!require(\"pacman\"))install.packages(\"pacman\"))\n", "\n", "pacman::p_load(tidyverse, tidymodels, themis, kernlab, ranger, xgboost, kknn)" ], "execution_count": null, "outputs": [] }, { "cell_type": "markdown", "metadata": { "id": "z22M-pj4g07x" }, "source": [ "## **1. Un mapa de clasificación**\n", "\n", "En nuestra [lección anterior](https://github.com/microsoft/ML-For-Beginners/tree/main/4-Classification/2-Classifiers-1), intentamos abordar la pregunta: ¿cómo elegimos entre múltiples modelos? En gran medida, depende de las características de los datos y del tipo de problema que queremos resolver (por ejemplo, ¿clasificación o regresión?).\n", "\n", "Anteriormente, aprendimos sobre las diversas opciones que tienes al clasificar datos utilizando la hoja de referencia de Microsoft. El marco de aprendizaje automático de Python, Scikit-learn, ofrece una hoja de referencia similar pero más detallada que puede ayudarte aún más a reducir tus estimadores (otro término para clasificadores):\n", "\n", "

\n", " \n", "

\n" ] }, { "cell_type": "markdown", "metadata": { "id": "u1i3xRIVg7vG" }, "source": [ "> Consejo: [visita este mapa en línea](https://scikit-learn.org/stable/tutorial/machine_learning_map/) y haz clic a lo largo del camino para leer la documentación. \n", "> \n", "> El [sitio de referencia de Tidymodels](https://www.tidymodels.org/find/parsnip/#models) también ofrece una excelente documentación sobre los diferentes tipos de modelos.\n", "\n", "### **El plan** 🗺️\n", "\n", "Este mapa es muy útil una vez que tienes un entendimiento claro de tus datos, ya que puedes 'caminar' por sus caminos hacia una decisión:\n", "\n", "- Tenemos \\>50 muestras\n", "\n", "- Queremos predecir una categoría\n", "\n", "- Tenemos datos etiquetados\n", "\n", "- Tenemos menos de 100K muestras\n", "\n", "- ✨ Podemos elegir un Linear SVC\n", "\n", "- Si eso no funciona, dado que tenemos datos numéricos\n", "\n", " - Podemos intentar un ✨ KNeighbors Classifier\n", "\n", " - Si eso tampoco funciona, probar ✨ SVC y ✨ Ensemble Classifiers\n", "\n", "Este es un camino muy útil a seguir. Ahora, vamos a sumergirnos directamente utilizando el marco de modelado [tidymodels](https://www.tidymodels.org/): una colección consistente y flexible de paquetes de R desarrollada para fomentar buenas prácticas estadísticas 😊.\n", "\n", "## 2. Dividir los datos y manejar un conjunto de datos desequilibrado.\n", "\n", "En nuestras lecciones anteriores, aprendimos que había un conjunto de ingredientes comunes entre nuestras cocinas. Además, había una distribución bastante desigual en el número de cocinas.\n", "\n", "Abordaremos esto de la siguiente manera:\n", "\n", "- Eliminando los ingredientes más comunes que generan confusión entre cocinas distintas, usando `dplyr::select()`.\n", "\n", "- Usando una `recipe` que preprocesa los datos para prepararlos para el modelado aplicando un algoritmo de `over-sampling`.\n", "\n", "Ya vimos lo anterior en la lección pasada, ¡así que esto será pan comido 🥳!\n" ] }, { "cell_type": "code", "metadata": { "id": "6tj_rN00hClA" }, "source": [ "# Load the core Tidyverse and Tidymodels packages\n", "library(tidyverse)\n", "library(tidymodels)\n", "\n", "# Load the original cuisines data\n", "df <- read_csv(file = \"https://raw.githubusercontent.com/microsoft/ML-For-Beginners/main/4-Classification/data/cuisines.csv\")\n", "\n", "# Drop id column, rice, garlic and ginger from our original data set\n", "df_select <- df %>% \n", " select(-c(1, rice, garlic, ginger)) %>%\n", " # Encode cuisine column as categorical\n", " mutate(cuisine = factor(cuisine))\n", "\n", "\n", "# Create data split specification\n", "set.seed(2056)\n", "cuisines_split <- initial_split(data = df_select,\n", " strata = cuisine,\n", " prop = 0.7)\n", "\n", "# Extract the data in each split\n", "cuisines_train <- training(cuisines_split)\n", "cuisines_test <- testing(cuisines_split)\n", "\n", "# Display distribution of cuisines in the training set\n", "cuisines_train %>% \n", " count(cuisine) %>% \n", " arrange(desc(n))" ], "execution_count": null, "outputs": [] }, { "cell_type": "markdown", "metadata": { "id": "zFin5yw3hHb1" }, "source": [ "### Cómo manejar datos desbalanceados\n", "\n", "Los datos desbalanceados a menudo tienen efectos negativos en el rendimiento del modelo. Muchos modelos funcionan mejor cuando el número de observaciones es igual y, por lo tanto, tienden a tener dificultades con datos desbalanceados.\n", "\n", "Existen principalmente dos formas de abordar conjuntos de datos desbalanceados:\n", "\n", "- agregar observaciones a la clase minoritaria: `Sobre-muestreo`, por ejemplo, utilizando un algoritmo SMOTE que genera de manera sintética nuevos ejemplos de la clase minoritaria utilizando los vecinos más cercanos de estos casos.\n", "\n", "- eliminar observaciones de la clase mayoritaria: `Sub-muestreo`\n", "\n", "En nuestra lección anterior, demostramos cómo manejar conjuntos de datos desbalanceados utilizando una `receta`. Una receta puede considerarse como un plan que describe qué pasos deben aplicarse a un conjunto de datos para prepararlo para el análisis. En nuestro caso, queremos tener una distribución equitativa en el número de nuestras categorías de cocina para nuestro `conjunto de entrenamiento`. Vamos a ello.\n" ] }, { "cell_type": "code", "metadata": { "id": "cRzTnHolhLWd" }, "source": [ "# Load themis package for dealing with imbalanced data\n", "library(themis)\n", "\n", "# Create a recipe for preprocessing training data\n", "cuisines_recipe <- recipe(cuisine ~ ., data = cuisines_train) %>%\n", " step_smote(cuisine) \n", "\n", "# Print recipe\n", "cuisines_recipe" ], "execution_count": null, "outputs": [] }, { "cell_type": "markdown", "metadata": { "id": "KxOQ2ORhhO81" }, "source": [ "¡Ahora estamos listos para entrenar modelos 👩‍💻👨‍💻!\n", "\n", "## 3. Más allá de los modelos de regresión multinomial\n", "\n", "En nuestra lección anterior, vimos los modelos de regresión multinomial. Ahora exploremos algunos modelos más flexibles para clasificación.\n", "\n", "### Máquinas de Vectores de Soporte\n", "\n", "En el contexto de la clasificación, las `Máquinas de Vectores de Soporte` son una técnica de aprendizaje automático que intenta encontrar un *hiperplano* que \"mejor\" separe las clases. Veamos un ejemplo sencillo:\n", "\n", "

\n", " \n", "

https://commons.wikimedia.org/w/index.php?curid=22877598
\n" ] }, { "cell_type": "markdown", "metadata": { "id": "C4Wsd0vZhXYu" }, "source": [ "H1~ no separa las clases. H2~ sí lo hace, pero solo con un pequeño margen. H3~ las separa con el margen máximo.\n", "\n", "#### Clasificador Lineal de Vectores de Soporte\n", "\n", "El clustering de vectores de soporte (SVC) es una técnica derivada de la familia de máquinas de vectores de soporte (SVM) en el aprendizaje automático. En SVC, el hiperplano se elige para separar correctamente a `la mayoría` de las observaciones de entrenamiento, pero `puede clasificar erróneamente` algunas observaciones. Al permitir que algunos puntos estén en el lado incorrecto, el SVM se vuelve más robusto frente a valores atípicos, lo que mejora su capacidad de generalización a nuevos datos. El parámetro que regula esta violación se denomina `coste`, y tiene un valor predeterminado de 1 (consulta `help(\"svm_poly\")`).\n", "\n", "Vamos a crear un SVC lineal configurando `degree = 1` en un modelo SVM polinómico.\n" ] }, { "cell_type": "code", "metadata": { "id": "vJpp6nuChlBz" }, "source": [ "# Make a linear SVC specification\n", "svc_linear_spec <- svm_poly(degree = 1) %>% \n", " set_engine(\"kernlab\") %>% \n", " set_mode(\"classification\")\n", "\n", "# Bundle specification and recipe into a worklow\n", "svc_linear_wf <- workflow() %>% \n", " add_recipe(cuisines_recipe) %>% \n", " add_model(svc_linear_spec)\n", "\n", "# Print out workflow\n", "svc_linear_wf" ], "execution_count": null, "outputs": [] }, { "cell_type": "markdown", "metadata": { "id": "rDs8cWNkhoqu" }, "source": [ "Ahora que hemos capturado los pasos de preprocesamiento y la especificación del modelo en un *workflow*, podemos proceder a entrenar el SVC lineal y evaluar los resultados al mismo tiempo. Para las métricas de rendimiento, vamos a crear un conjunto de métricas que evalúe: `accuracy`, `sensitivity`, `Positive Predicted Value` y `F Measure`.\n", "\n", "> `augment()` añadirá columna(s) con las predicciones a los datos proporcionados.\n" ] }, { "cell_type": "code", "metadata": { "id": "81wiqcwuhrnq" }, "source": [ "# Train a linear SVC model\n", "svc_linear_fit <- svc_linear_wf %>% \n", " fit(data = cuisines_train)\n", "\n", "# Create a metric set\n", "eval_metrics <- metric_set(ppv, sens, accuracy, f_meas)\n", "\n", "\n", "# Make predictions and Evaluate model performance\n", "svc_linear_fit %>% \n", " augment(new_data = cuisines_test) %>% \n", " eval_metrics(truth = cuisine, estimate = .pred_class)" ], "execution_count": null, "outputs": [] }, { "cell_type": "markdown", "metadata": { "id": "0UFQvHf-huo3" }, "source": [ "#### Máquina de Vectores de Soporte\n", "\n", "La máquina de vectores de soporte (SVM, por sus siglas en inglés) es una extensión del clasificador de vectores de soporte diseñada para manejar límites no lineales entre las clases. En esencia, las SVM utilizan el *truco del kernel* para ampliar el espacio de características y adaptarse a relaciones no lineales entre las clases. Una función kernel popular y extremadamente flexible que utilizan las SVM es la *función de base radial.* Veamos cómo se desempeña con nuestros datos.\n" ] }, { "cell_type": "code", "metadata": { "id": "-KX4S8mzhzmp" }, "source": [ "set.seed(2056)\n", "\n", "# Make an RBF SVM specification\n", "svm_rbf_spec <- svm_rbf() %>% \n", " set_engine(\"kernlab\") %>% \n", " set_mode(\"classification\")\n", "\n", "# Bundle specification and recipe into a worklow\n", "svm_rbf_wf <- workflow() %>% \n", " add_recipe(cuisines_recipe) %>% \n", " add_model(svm_rbf_spec)\n", "\n", "\n", "# Train an RBF model\n", "svm_rbf_fit <- svm_rbf_wf %>% \n", " fit(data = cuisines_train)\n", "\n", "\n", "# Make predictions and Evaluate model performance\n", "svm_rbf_fit %>% \n", " augment(new_data = cuisines_test) %>% \n", " eval_metrics(truth = cuisine, estimate = .pred_class)" ], "execution_count": null, "outputs": [] }, { "cell_type": "markdown", "metadata": { "id": "QBFSa7WSh4HQ" }, "source": [ "¡Mucho mejor 🤩!\n", "\n", "> ✅ Por favor consulta:\n", ">\n", "> - [*Support Vector Machines*](https://bradleyboehmke.github.io/HOML/svm.html), Hands-on Machine Learning with R\n", ">\n", "> - [*Support Vector Machines*](https://www.statlearning.com/), An Introduction to Statistical Learning with Applications in R\n", ">\n", "> para más información.\n", "\n", "### Clasificadores de Vecinos Más Cercanos\n", "\n", "El algoritmo de *K*-vecinos más cercanos (KNN) predice cada observación basándose en su *similitud* con otras observaciones.\n", "\n", "Vamos a ajustarlo a nuestros datos.\n" ] }, { "cell_type": "code", "metadata": { "id": "k4BxxBcdh9Ka" }, "source": [ "# Make a KNN specification\n", "knn_spec <- nearest_neighbor() %>% \n", " set_engine(\"kknn\") %>% \n", " set_mode(\"classification\")\n", "\n", "# Bundle recipe and model specification into a workflow\n", "knn_wf <- workflow() %>% \n", " add_recipe(cuisines_recipe) %>% \n", " add_model(knn_spec)\n", "\n", "# Train a boosted tree model\n", "knn_wf_fit <- knn_wf %>% \n", " fit(data = cuisines_train)\n", "\n", "\n", "# Make predictions and Evaluate model performance\n", "knn_wf_fit %>% \n", " augment(new_data = cuisines_test) %>% \n", " eval_metrics(truth = cuisine, estimate = .pred_class)" ], "execution_count": null, "outputs": [] }, { "cell_type": "markdown", "metadata": { "id": "HaegQseriAcj" }, "source": [ "Parece que este modelo no está funcionando tan bien. Probablemente cambiar los argumentos del modelo (consulta `help(\"nearest_neighbor\")`) mejorará el rendimiento del modelo. Asegúrate de probarlo.\n", "\n", "> ✅ Por favor consulta:\n", ">\n", "> - [Hands-on Machine Learning with R](https://bradleyboehmke.github.io/HOML/)\n", ">\n", "> - [An Introduction to Statistical Learning with Applications in R](https://www.statlearning.com/)\n", ">\n", "> para aprender más sobre los clasificadores *K*-Nearest Neighbors.\n", "\n", "### Clasificadores en conjunto\n", "\n", "Los algoritmos en conjunto funcionan combinando múltiples estimadores base para producir un modelo óptimo, ya sea mediante:\n", "\n", "`bagging`: aplicando una *función de promediado* a una colección de modelos base\n", "\n", "`boosting`: construyendo una secuencia de modelos que se basan unos en otros para mejorar el rendimiento predictivo.\n", "\n", "Comencemos probando un modelo de Random Forest, que construye una gran colección de árboles de decisión y luego aplica una función de promediado para obtener un modelo general mejorado.\n" ] }, { "cell_type": "code", "metadata": { "id": "49DPoVs6iK1M" }, "source": [ "# Make a random forest specification\n", "rf_spec <- rand_forest() %>% \n", " set_engine(\"ranger\") %>% \n", " set_mode(\"classification\")\n", "\n", "# Bundle recipe and model specification into a workflow\n", "rf_wf <- workflow() %>% \n", " add_recipe(cuisines_recipe) %>% \n", " add_model(rf_spec)\n", "\n", "# Train a random forest model\n", "rf_wf_fit <- rf_wf %>% \n", " fit(data = cuisines_train)\n", "\n", "\n", "# Make predictions and Evaluate model performance\n", "rf_wf_fit %>% \n", " augment(new_data = cuisines_test) %>% \n", " eval_metrics(truth = cuisine, estimate = .pred_class)" ], "execution_count": null, "outputs": [] }, { "cell_type": "markdown", "metadata": { "id": "RGVYwC_aiUWc" }, "source": [ "¡Buen trabajo 👏!\n", "\n", "Vamos a experimentar también con un modelo de Árbol Potenciado.\n", "\n", "El Árbol Potenciado define un método de conjunto que crea una serie de árboles de decisión secuenciales, donde cada árbol depende de los resultados de los árboles anteriores en un intento de reducir el error de manera incremental. Se enfoca en los pesos de los elementos clasificados incorrectamente y ajusta el modelo del siguiente clasificador para corregirlos.\n", "\n", "Existen diferentes formas de ajustar este modelo (consulta `help(\"boost_tree\")`). En este ejemplo, ajustaremos Árboles Potenciados utilizando el motor `xgboost`.\n" ] }, { "cell_type": "code", "metadata": { "id": "Py1YWo-micWs" }, "source": [ "# Make a boosted tree specification\n", "boost_spec <- boost_tree(trees = 200) %>% \n", " set_engine(\"xgboost\") %>% \n", " set_mode(\"classification\")\n", "\n", "# Bundle recipe and model specification into a workflow\n", "boost_wf <- workflow() %>% \n", " add_recipe(cuisines_recipe) %>% \n", " add_model(boost_spec)\n", "\n", "# Train a boosted tree model\n", "boost_wf_fit <- boost_wf %>% \n", " fit(data = cuisines_train)\n", "\n", "\n", "# Make predictions and Evaluate model performance\n", "boost_wf_fit %>% \n", " augment(new_data = cuisines_test) %>% \n", " eval_metrics(truth = cuisine, estimate = .pred_class)" ], "execution_count": null, "outputs": [] }, { "cell_type": "markdown", "metadata": { "id": "zNQnbuejigZM" }, "source": [ "> ✅ Por favor consulta:\n", ">\n", "> - [Machine Learning for Social Scientists](https://cimentadaj.github.io/ml_socsci/tree-based-methods.html#random-forests)\n", ">\n", "> - [Hands-on Machine Learning with R](https://bradleyboehmke.github.io/HOML/)\n", ">\n", "> - [An Introduction to Statistical Learning with Applications in R](https://www.statlearning.com/)\n", ">\n", "> - - Explora el modelo AdaBoost, que es una buena alternativa a xgboost.\n", ">\n", "> para aprender más sobre clasificadores Ensemble.\n", "\n", "## 4. Extra - comparando múltiples modelos\n", "\n", "Hemos ajustado bastantes modelos en este laboratorio 🙌. Puede volverse tedioso o complicado crear muchos flujos de trabajo a partir de diferentes conjuntos de preprocesadores y/o especificaciones de modelos y luego calcular las métricas de rendimiento una por una.\n", "\n", "Veamos si podemos abordar esto creando una función que ajuste una lista de flujos de trabajo en el conjunto de entrenamiento y luego devuelva las métricas de rendimiento basadas en el conjunto de prueba. Usaremos `map()` y `map_dfr()` del paquete [purrr](https://purrr.tidyverse.org/) para aplicar funciones a cada elemento de una lista.\n", "\n", "> Las funciones [`map()`](https://purrr.tidyverse.org/reference/map.html) te permiten reemplazar muchos bucles for con un código que es más conciso y fácil de leer. El mejor lugar para aprender sobre las funciones [`map()`](https://purrr.tidyverse.org/reference/map.html) es el [capítulo de iteración](http://r4ds.had.co.nz/iteration.html) en R for Data Science.\n" ] }, { "cell_type": "code", "metadata": { "id": "Qzb7LyZnimd2" }, "source": [ "set.seed(2056)\n", "\n", "# Create a metric set\n", "eval_metrics <- metric_set(ppv, sens, accuracy, f_meas)\n", "\n", "# Define a function that returns performance metrics\n", "compare_models <- function(workflow_list, train_set, test_set){\n", " \n", " suppressWarnings(\n", " # Fit each model to the train_set\n", " map(workflow_list, fit, data = train_set) %>% \n", " # Make predictions on the test set\n", " map_dfr(augment, new_data = test_set, .id = \"model\") %>%\n", " # Select desired columns\n", " select(model, cuisine, .pred_class) %>% \n", " # Evaluate model performance\n", " group_by(model) %>% \n", " eval_metrics(truth = cuisine, estimate = .pred_class) %>% \n", " ungroup()\n", " )\n", " \n", "} # End of function" ], "execution_count": null, "outputs": [] }, { "cell_type": "markdown", "metadata": { "id": "Fwa712sNisDA" }, "source": [] }, { "cell_type": "code", "metadata": { "id": "3i4VJOi2iu-a" }, "source": [ "# Make a list of workflows\n", "workflow_list <- list(\n", " \"svc\" = svc_linear_wf,\n", " \"svm\" = svm_rbf_wf,\n", " \"knn\" = knn_wf,\n", " \"random_forest\" = rf_wf,\n", " \"xgboost\" = boost_wf)\n", "\n", "# Call the function\n", "set.seed(2056)\n", "perf_metrics <- compare_models(workflow_list = workflow_list, train_set = cuisines_train, test_set = cuisines_test)\n", "\n", "# Print out performance metrics\n", "perf_metrics %>% \n", " group_by(.metric) %>% \n", " arrange(desc(.estimate)) %>% \n", " slice_head(n=7)\n", "\n", "# Compare accuracy\n", "perf_metrics %>% \n", " filter(.metric == \"accuracy\") %>% \n", " arrange(desc(.estimate))\n" ], "execution_count": null, "outputs": [] }, { "cell_type": "markdown", "metadata": { "id": "KuWK_lEli4nW" }, "source": [ "El paquete [**workflowset**](https://workflowsets.tidymodels.org/) permite a los usuarios crear y ajustar fácilmente una gran cantidad de modelos, pero está diseñado principalmente para trabajar con técnicas de remuestreo como `cross-validation`, un enfoque que aún no hemos cubierto.\n", "\n", "## **🚀Desafío**\n", "\n", "Cada una de estas técnicas tiene una gran cantidad de parámetros que puedes ajustar, por ejemplo, `cost` en SVMs, `neighbors` en KNN, `mtry` (Predictores Seleccionados Aleatoriamente) en Random Forest.\n", "\n", "Investiga los parámetros predeterminados de cada uno y piensa en lo que significaría ajustar estos parámetros para la calidad del modelo.\n", "\n", "Para obtener más información sobre un modelo en particular y sus parámetros, utiliza: `help(\"model\")`, por ejemplo, `help(\"rand_forest\")`.\n", "\n", "> En la práctica, usualmente *estimamos* los *mejores valores* para estos entrenando muchos modelos en un `conjunto de datos simulado` y midiendo qué tan bien funcionan todos estos modelos. Este proceso se llama **ajuste**.\n", "\n", "### [**Cuestionario post-clase**](https://gray-sand-07a10f403.1.azurestaticapps.net/quiz/24/)\n", "\n", "### **Revisión y Estudio Personal**\n", "\n", "Hay mucho vocabulario técnico en estas lecciones, así que tómate un momento para revisar [esta lista](https://docs.microsoft.com/dotnet/machine-learning/resources/glossary?WT.mc_id=academic-77952-leestott) de terminología útil.\n", "\n", "#### GRACIAS A:\n", "\n", "[`Allison Horst`](https://twitter.com/allison_horst/) por crear las increíbles ilustraciones que hacen que R sea más acogedor y atractivo. Encuentra más ilustraciones en su [galería](https://www.google.com/url?q=https://github.com/allisonhorst/stats-illustrations&sa=D&source=editors&ust=1626380772530000&usg=AOvVaw3zcfyCizFQZpkSLzxiiQEM).\n", "\n", "[Cassie Breviu](https://www.twitter.com/cassieview) y [Jen Looper](https://www.twitter.com/jenlooper) por crear la versión original en Python de este módulo ♥️\n", "\n", "Feliz aprendizaje,\n", "\n", "[Eric](https://twitter.com/ericntay), Embajador Estudiantil de Microsoft Learn Gold.\n", "\n", "

\n", " \n", "

Arte por @allison_horst
\n" ] }, { "cell_type": "markdown", "metadata": {}, "source": [ "\n---\n\n**Descargo de responsabilidad**: \nEste documento ha sido traducido utilizando el servicio de traducción automática [Co-op Translator](https://github.com/Azure/co-op-translator). Si bien nos esforzamos por lograr precisión, tenga en cuenta que las traducciones automáticas pueden contener errores o imprecisiones. El documento original en su idioma nativo debe considerarse como la fuente autorizada. Para información crítica, se recomienda una traducción profesional realizada por humanos. No nos hacemos responsables de malentendidos o interpretaciones erróneas que puedan surgir del uso de esta traducción.\n" ] } ] }