{ "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-04T08:36:11+00:00", "source_file": "4-Classification/3-Classifiers-2/solution/R/lesson_12-R.ipynb", "language_code": "da" } }, "cells": [ { "cell_type": "markdown", "metadata": { "id": "jsFutf_ygqSx" }, "source": [ "# Byg en klassifikationsmodel: Lækre asiatiske og indiske retter\n" ] }, { "cell_type": "markdown", "metadata": { "id": "HD54bEefgtNO" }, "source": [ "## Klassifikatorer for køkken 2\n", "\n", "I denne anden lektion om klassifikation vil vi udforske `flere måder` at klassificere kategoriske data på. Vi vil også lære om konsekvenserne ved at vælge én klassifikator frem for en anden.\n", "\n", "### [**Quiz før lektionen**](https://gray-sand-07a10f403.1.azurestaticapps.net/quiz/23/)\n", "\n", "### **Forudsætninger**\n", "\n", "Vi antager, at du har gennemført de tidligere lektioner, da vi vil bygge videre på nogle af de begreber, vi tidligere har lært.\n", "\n", "Til denne lektion skal vi bruge følgende pakker:\n", "\n", "- `tidyverse`: [tidyverse](https://www.tidyverse.org/) er en [samling af R-pakker](https://www.tidyverse.org/packages), der er designet til at gøre datavidenskab hurtigere, nemmere og sjovere!\n", "\n", "- `tidymodels`: [tidymodels](https://www.tidymodels.org/) er en [rammeværk af pakker](https://www.tidymodels.org/packages/) til modellering og maskinlæring.\n", "\n", "- `themis`: [themis-pakken](https://themis.tidymodels.org/) tilbyder ekstra opskridt til håndtering af ubalancerede data.\n", "\n", "Du kan installere dem med følgende kommando:\n", "\n", "`install.packages(c(\"tidyverse\", \"tidymodels\", \"kernlab\", \"themis\", \"ranger\", \"xgboost\", \"kknn\"))`\n", "\n", "Alternativt kan scriptet nedenfor kontrollere, om du har de nødvendige pakker til at gennemføre dette modul, og installere dem for dig, hvis de mangler.\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. Et klassifikationskort**\n", "\n", "I vores [forrige lektion](https://github.com/microsoft/ML-For-Beginners/tree/main/4-Classification/2-Classifiers-1) forsøgte vi at besvare spørgsmålet: hvordan vælger vi mellem flere modeller? I høj grad afhænger det af dataenes karakteristika og typen af problem, vi ønsker at løse (for eksempel klassifikation eller regression).\n", "\n", "Tidligere lærte vi om de forskellige muligheder, du har, når du klassificerer data ved hjælp af Microsofts cheat sheet. Python's Machine Learning-framework, Scikit-learn, tilbyder et lignende, men mere detaljeret cheat sheet, der yderligere kan hjælpe med at indsnævre dine estimators (et andet ord for klassifikatorer):\n", "\n", "

\n", " \n", "

\n" ] }, { "cell_type": "markdown", "metadata": { "id": "u1i3xRIVg7vG" }, "source": [ "> Tip: [besøg dette kort online](https://scikit-learn.org/stable/tutorial/machine_learning_map/) og klik langs stien for at læse dokumentationen.\n", ">\n", "> [Tidymodels reference-siden](https://www.tidymodels.org/find/parsnip/#models) giver også fremragende dokumentation om forskellige typer modeller.\n", "\n", "### **Planen** 🗺️\n", "\n", "Dette kort er meget nyttigt, når du har et klart overblik over dine data, da du kan 'gå' langs dets stier til en beslutning:\n", "\n", "- Vi har \\>50 prøver\n", "\n", "- Vi ønsker at forudsige en kategori\n", "\n", "- Vi har mærkede data\n", "\n", "- Vi har færre end 100K prøver\n", "\n", "- ✨ Vi kan vælge en Linear SVC\n", "\n", "- Hvis det ikke virker, da vi har numeriske data\n", "\n", " - Kan vi prøve en ✨ KNeighbors Classifier\n", "\n", " - Hvis det heller ikke virker, prøv ✨ SVC og ✨ Ensemble Classifiers\n", "\n", "Dette er en meget nyttig sti at følge. Lad os nu komme i gang med at bruge [tidymodels](https://www.tidymodels.org/) modelleringsrammen: en konsistent og fleksibel samling af R-pakker udviklet til at fremme god statistisk praksis 😊.\n", "\n", "## 2. Opdel dataene og håndter ubalancerede datasæt.\n", "\n", "Fra vores tidligere lektioner lærte vi, at der var et sæt af fælles ingredienser på tværs af vores køkkener. Derudover var der en ret ulige fordeling i antallet af køkkener.\n", "\n", "Vi vil håndtere dette ved at\n", "\n", "- Fjerne de mest almindelige ingredienser, der skaber forvirring mellem forskellige køkkener, ved hjælp af `dplyr::select()`.\n", "\n", "- Bruge en `recipe`, der forbehandler dataene, så de er klar til modellering, ved at anvende en `over-sampling` algoritme.\n", "\n", "Vi har allerede kigget på ovenstående i den tidligere lektion, så dette burde være en leg 🥳!\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": [ "### Håndtering af ubalancerede data\n", "\n", "Ubalancerede data har ofte en negativ indvirkning på modellens ydeevne. Mange modeller fungerer bedst, når antallet af observationer er lige, og har derfor tendens til at kæmpe med ubalancerede data.\n", "\n", "Der er primært to måder at håndtere ubalancerede datasæt på:\n", "\n", "- tilføje observationer til minoritetsklassen: `Over-sampling`, f.eks. ved brug af en SMOTE-algoritme, som syntetisk genererer nye eksempler af minoritetsklassen ved hjælp af nærmeste naboer til disse tilfælde.\n", "\n", "- fjerne observationer fra majoritetsklassen: `Under-sampling`\n", "\n", "I vores tidligere lektion demonstrerede vi, hvordan man håndterer ubalancerede datasæt ved hjælp af en `recipe`. En recipe kan betragtes som en køreplan, der beskriver, hvilke trin der skal anvendes på et datasæt for at gøre det klar til dataanalyse. I vores tilfælde ønsker vi at have en lige fordeling i antallet af vores køkkener for vores `training set`. Lad os komme i gang.\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": [ "Nu er vi klar til at træne modeller 👩‍💻👨‍💻!\n", "\n", "## 3. Ud over multinomial regressionsmodeller\n", "\n", "I vores tidligere lektion kiggede vi på multinomial regressionsmodeller. Lad os udforske nogle mere fleksible modeller til klassifikation.\n", "\n", "### Support Vector Machines\n", "\n", "I forbindelse med klassifikation er `Support Vector Machines` en maskinlæringsteknik, der forsøger at finde et *hyperplan*, som \"bedst\" adskiller klasserne. Lad os se på et simpelt eksempel:\n", "\n", "

\n", " \n", "

https://commons.wikimedia.org/w/index.php?curid=22877598
\n" ] }, { "cell_type": "markdown", "metadata": { "id": "C4Wsd0vZhXYu" }, "source": [ "H1~ adskiller ikke klasserne. H2~ gør det, men kun med en lille margin. H3~ adskiller dem med den maksimale margin.\n", "\n", "#### Lineær Support Vector Classifier\n", "\n", "Support-Vector clustering (SVC) er en del af Support-Vector maskinerne, som er en familie af ML-teknikker. I SVC vælges hyperplanet, så det korrekt adskiller `de fleste` af træningsobservationerne, men `kan fejlagtigt klassificere` nogle få observationer. Ved at tillade, at nogle punkter er på den forkerte side, bliver SVM mere robust over for outliers og dermed bedre til at generalisere til nye data. Den parameter, der regulerer denne overtrædelse, kaldes `cost`, som har en standardværdi på 1 (se `help(\"svm_poly\")`).\n", "\n", "Lad os oprette en lineær SVC ved at sætte `degree = 1` i en polynomisk SVM-model.\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": [ "Nu hvor vi har samlet forbehandlingsskridtene og modelspecifikationen i en *workflow*, kan vi gå videre og træne den lineære SVC og evaluere resultaterne samtidig. For at måle ydeevnen, lad os oprette et sæt af metrikker, der vil evaluere: `accuracy`, `sensitivity`, `Positive Predicted Value` og `F Measure`.\n", "\n", "> `augment()` vil tilføje kolonne(r) med forudsigelser til de givne data.\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": [ "#### Support Vector Machine\n", "\n", "Support vector maskinen (SVM) er en udvidelse af support vector klassifikatoren for at kunne håndtere en ikke-lineær grænse mellem klasserne. Grundlæggende bruger SVM'er *kernel-tricket* til at udvide feature-rummet for at tilpasse sig ikke-lineære relationer mellem klasserne. En populær og meget fleksibel kernel-funktion, der bruges af SVM'er, er *Radial basis function.* Lad os se, hvordan den klarer sig på vores data.\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": [ "Meget bedre 🤩!\n", "\n", "> ✅ Se venligst:\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", "> for yderligere læsning.\n", "\n", "### Nærmeste nabo-klassifikatorer\n", "\n", "*K*-nærmeste nabo (KNN) er en algoritme, hvor hver observation forudsiges baseret på dens *lighed* med andre observationer.\n", "\n", "Lad os tilpasse en til vores data.\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": [ "Det ser ud til, at denne model ikke klarer sig så godt. Det er sandsynligt, at ændring af modellens parametre (se `help(\"nearest_neighbor\")`) vil forbedre modellens ydeevne. Sørg for at prøve det.\n", "\n", "> ✅ Se venligst:\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", "> for at lære mere om *K*-Nearest Neighbors klassifikatorer.\n", "\n", "### Ensemble klassifikatorer\n", "\n", "Ensemble-algoritmer fungerer ved at kombinere flere basismodeller for at producere en optimal model enten ved:\n", "\n", "`bagging`: anvendelse af en *gennemsnitsfunktion* på en samling af basismodeller\n", "\n", "`boosting`: opbygning af en sekvens af modeller, der bygger på hinanden for at forbedre den prædiktive ydeevne.\n", "\n", "Lad os starte med at prøve en Random Forest-model, som opbygger en stor samling af beslutningstræer og derefter anvender en gennemsnitsfunktion for at skabe en bedre samlet model.\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": [ "Godt klaret 👏!\n", "\n", "Lad os også eksperimentere med en Boosted Tree-model.\n", "\n", "Boosted Tree definerer en ensemblemetode, der skaber en række sekventielle beslutningstræer, hvor hvert træ afhænger af resultaterne fra de tidligere træer i et forsøg på gradvist at reducere fejlen. Den fokuserer på vægten af forkert klassificerede elementer og justerer tilpasningen for den næste klassifikator for at rette op.\n", "\n", "Der er forskellige måder at tilpasse denne model på (se `help(\"boost_tree\")`). I dette eksempel vil vi tilpasse Boosted Trees via `xgboost`-motoren.\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": [ "> ✅ Se:\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", "> - - Udforsker AdaBoost-modellen, som er et godt alternativ til xgboost.\n", ">\n", "> for at lære mere om Ensemble-klassifikatorer.\n", "\n", "## 4. Ekstra - sammenligning af flere modeller\n", "\n", "Vi har tilpasset en hel del modeller i denne øvelse 🙌. Det kan blive tidskrævende eller besværligt at oprette mange workflows fra forskellige sæt af preprocessorer og/eller modelspecifikationer og derefter beregne præstationsmålinger én efter én.\n", "\n", "Lad os se, om vi kan løse dette ved at oprette en funktion, der tilpasser en liste af workflows på træningssættet og derefter returnerer præstationsmålingerne baseret på test-sættet. Vi kommer til at bruge `map()` og `map_dfr()` fra [purrr](https://purrr.tidyverse.org/) pakken til at anvende funktioner på hvert element i en liste.\n", "\n", "> [`map()`](https://purrr.tidyverse.org/reference/map.html) funktioner giver dig mulighed for at erstatte mange for-løkker med kode, der både er mere kortfattet og lettere at læse. Det bedste sted at lære om [`map()`](https://purrr.tidyverse.org/reference/map.html) funktionerne er [iteration-kapitlet](http://r4ds.had.co.nz/iteration.html) i 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": [ "[**workflowset**](https://workflowsets.tidymodels.org/) pakken giver brugerne mulighed for at oprette og nemt tilpasse et stort antal modeller, men den er primært designet til at arbejde med genprøvningsteknikker som f.eks. `cross-validation`, en metode vi endnu ikke har dækket.\n", "\n", "## **🚀Udfordring**\n", "\n", "Hver af disse teknikker har et stort antal parametre, som du kan justere, for eksempel `cost` i SVM'er, `neighbors` i KNN, `mtry` (Tilfældigt Valgte Predictors) i Random Forest.\n", "\n", "Undersøg standardparametrene for hver enkelt og overvej, hvad det ville betyde for modellens kvalitet at justere disse parametre.\n", "\n", "For at finde mere information om en bestemt model og dens parametre, brug: `help(\"model\")`, f.eks. `help(\"rand_forest\")`.\n", "\n", "> I praksis *estimerer* vi normalt de *bedste værdier* for disse ved at træne mange modeller på et `simuleret datasæt` og måle, hvor godt alle disse modeller præsterer. Denne proces kaldes **tuning**.\n", "\n", "### [**Quiz efter forelæsningen**](https://gray-sand-07a10f403.1.azurestaticapps.net/quiz/24/)\n", "\n", "### **Gennemgang & Selvstudie**\n", "\n", "Der er mange fagudtryk i disse lektioner, så tag et øjeblik til at gennemgå [denne liste](https://docs.microsoft.com/dotnet/machine-learning/resources/glossary?WT.mc_id=academic-77952-leestott) med nyttig terminologi!\n", "\n", "#### TAK TIL:\n", "\n", "[`Allison Horst`](https://twitter.com/allison_horst/) for at skabe de fantastiske illustrationer, der gør R mere imødekommende og engagerende. Find flere illustrationer i hendes [galleri](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) og [Jen Looper](https://www.twitter.com/jenlooper) for at skabe den originale Python-version af dette modul ♥️\n", "\n", "God læring,\n", "\n", "[Eric](https://twitter.com/ericntay), Gold Microsoft Learn Student Ambassador.\n", "\n", "

\n", " \n", "

Illustration af @allison_horst
\n" ] }, { "cell_type": "markdown", "metadata": {}, "source": [ "\n---\n\n**Ansvarsfraskrivelse**: \nDette dokument er blevet oversat ved hjælp af AI-oversættelsestjenesten [Co-op Translator](https://github.com/Azure/co-op-translator). Selvom vi bestræber os på at sikre nøjagtighed, skal du være opmærksom på, at automatiserede oversættelser kan indeholde fejl eller unøjagtigheder. Det originale dokument på dets oprindelige sprog bør betragtes som den autoritative kilde. For kritisk information anbefales professionel menneskelig oversættelse. Vi påtager os intet ansvar for misforståelser eller fejltolkninger, der måtte opstå som følge af brugen af denne oversættelse.\n" ] } ] }