<?xml version="1.0" encoding="UTF-8"?><rss xmlns:dc="http://purl.org/dc/elements/1.1/" xmlns:content="http://purl.org/rss/1.0/modules/content/" xmlns:atom="http://www.w3.org/2005/Atom" version="2.0" xmlns:itunes="http://www.itunes.com/dtds/podcast-1.0.dtd" xmlns:googleplay="http://www.google.com/schemas/play-podcasts/1.0"><channel><title><![CDATA[De substack van python-cursus.nl ]]></title><description><![CDATA[Nieuws en tutorials over Python]]></description><link>https://pythoncursus.substack.com</link><image><url>https://substackcdn.com/image/fetch/$s_!pKDK!,w_256,c_limit,f_auto,q_auto:good,fl_progressive:steep/https%3A%2F%2Fsubstack-post-media.s3.amazonaws.com%2Fpublic%2Fimages%2Fa3b379a6-a617-4c3d-a31f-f21f2f1fd0c4_750x750.png</url><title>De substack van python-cursus.nl </title><link>https://pythoncursus.substack.com</link></image><generator>Substack</generator><lastBuildDate>Fri, 21 Aug 2026 21:04:47 GMT</lastBuildDate><atom:link href="https://pythoncursus.substack.com/feed" rel="self" type="application/rss+xml"/><copyright><![CDATA[python-cursus.nl]]></copyright><language><![CDATA[en]]></language><webMaster><![CDATA[nieuwsbrief@python-cursus.nl]]></webMaster><itunes:owner><itunes:email><![CDATA[nieuwsbrief@python-cursus.nl]]></itunes:email><itunes:name><![CDATA[python-cursus.nl]]></itunes:name></itunes:owner><itunes:author><![CDATA[python-cursus.nl]]></itunes:author><googleplay:owner><![CDATA[nieuwsbrief@python-cursus.nl]]></googleplay:owner><googleplay:email><![CDATA[nieuwsbrief@python-cursus.nl]]></googleplay:email><googleplay:author><![CDATA[python-cursus.nl]]></googleplay:author><itunes:block><![CDATA[Yes]]></itunes:block><item><title><![CDATA[Gegevens opschonen met pandas]]></title><description><![CDATA[Leer hoe je datasets opschoont met pandas door missende waarden te behandelen, duplicaten te verwijderen en data types te converteren. Praktische technieken met dropna(), fillna() en to_numeric() aan de hand van KNMI-temperatuurdata.]]></description><link>https://pythoncursus.substack.com/p/gegevens-opschonen-met-pandas</link><guid isPermaLink="false">https://pythoncursus.substack.com/p/gegevens-opschonen-met-pandas</guid><dc:creator><![CDATA[python-cursus.nl]]></dc:creator><pubDate>Tue, 25 Nov 2025 15:36:57 GMT</pubDate><enclosure url="https://substack-post-media.s3.amazonaws.com/public/images/8e044904-e2a1-4a30-b87f-d4b28aee782c_1024x1024.webp" length="0" type="image/jpeg"/><content:encoded><![CDATA[<p>Het is een poos geleden, maar eindelijk is er weer een nieuwe blogpost. En wel over het meest gelezen onderwerp op de site: <a href="https://python-cursus.nl/blog/?series=pandas">Werken met pandas</a>!</p><p>In deze tutorial leer je hoe je een vervuilde dataset opschoont voor je er analyses op los laat.</p><p>In de praktijk zijn datasets namelijk zelden perfect gestructureerd. Je komt vaak missende waarden tegen, duplicaten in je data, of verkeerde data types die analyses onmogelijk maken. In deze tutorial leer je hoe je deze problemen oplost met pandas. Je ontdekt hoe je missende waarden detecteert en behandelt met <code>dropna()</code> en <code>fillna()</code>, duplicaten opspoort en verwijdert, en data types controleert en converteert naar het juiste formaat.</p><p>Veel lees- en leerplezier!</p><p class="button-wrapper" data-attrs="{&quot;url&quot;:&quot;https://python-cursus.nl/blog/pandas-gegevens-opschonen/&quot;,&quot;text&quot;:&quot;Naar de tutorial&quot;,&quot;action&quot;:null,&quot;class&quot;:null}" data-component-name="ButtonCreateButton"><a class="button primary" href="https://python-cursus.nl/blog/pandas-gegevens-opschonen/"><span>Naar de tutorial</span></a></p><p></p>]]></content:encoded></item><item><title><![CDATA[Nicky maakt programmeren met Python toegankelijk en leuk]]></title><description><![CDATA[Nicky van NK Academy is de eerste aanbieder van trainingen waar python-cursus.nl een samenwerking mee heeft.]]></description><link>https://pythoncursus.substack.com/p/nicky-maakt-programmeren-met-python</link><guid isPermaLink="false">https://pythoncursus.substack.com/p/nicky-maakt-programmeren-met-python</guid><dc:creator><![CDATA[python-cursus.nl]]></dc:creator><pubDate>Tue, 17 Jun 2025 06:01:28 GMT</pubDate><enclosure url="https://substack-post-media.s3.amazonaws.com/public/images/fc27a47a-2b18-4d3e-a27b-fd97a3c52d0e_1024x1024.webp" length="0" type="image/jpeg"/><content:encoded><![CDATA[<p><strong>Nicky van NK Academy is de eerste aanbieder van trainingen waar python-cursus.nl een samenwerking mee heeft. Ze biedt een online cursus voor beginners, geen voorkennis benodigd!<br><br></strong>Ben je nieuwsgierig naar programmeren, maar weet je niet waar je moet beginnen? Nicky van NK Academy heeft de perfecte oplossing: de online cursus <strong>Python voor Niet-Programmeurs</strong>. Speciaal ontworpen voor absolute beginners, biedt deze cursus een toegankelijke en plezierige introductie tot de wereld van programmeren.</p><p>In korte, duidelijke video's legt Nicky de basisprincipes van programmeren uit, zoals variabelen, datatypes en functies. Je hebt geen technische of wiskundige voorkennis nodig. Met slechts 1 tot 3 uur per week kun je in zes weken al je eerste Python-scripts schrijven. Mocht je ergens vastlopen of vragen hebben, dan staat er een besloten online community klaar om je te ondersteunen.</p><p>Of je nu je digitale vaardigheden wilt verbeteren, een carri&#232;reswitch overweegt of gewoon iets nieuws wilt leren, deze cursus is een uitstekende start. Nicky's aanpak maakt programmeren begrijpelijk en leuk voor iedereen.</p><p class="button-wrapper" data-attrs="{&quot;url&quot;:&quot;https://python-cursus.nl/trainingen/&quot;,&quot;text&quot;:&quot;Bekijk het aanbod van NK Academy&quot;,&quot;action&quot;:null,&quot;class&quot;:null}" data-component-name="ButtonCreateButton"><a class="button primary" href="https://python-cursus.nl/trainingen/"><span>Bekijk het aanbod van NK Academy</span></a></p><p></p>]]></content:encoded></item><item><title><![CDATA[Vervolgtutorial in Django-reeks]]></title><description><![CDATA[Django biedt een krachtig systeem om gegevens in een database op te slaan en te beheren via models. In deze tutorial leer je hoe je models definieert, migraties uitvoert om je database structuur te cre&#235;ren, en de Django Admin gebruikt om gegevens toe te voegen. Je leert ook hoe je met queries data uit je database haalt om in je templates te tonen.]]></description><link>https://pythoncursus.substack.com/p/vervolgtutorial-in-django-reeks</link><guid isPermaLink="false">https://pythoncursus.substack.com/p/vervolgtutorial-in-django-reeks</guid><dc:creator><![CDATA[python-cursus.nl]]></dc:creator><pubDate>Thu, 08 May 2025 09:09:06 GMT</pubDate><enclosure url="https://substack-post-media.s3.amazonaws.com/public/images/7269ccfa-d557-46aa-80c5-2d0d4e5dd229_256x256.webp" length="0" type="image/jpeg"/><content:encoded><![CDATA[<p>In de reeks <a href="https://python-cursus.nl/blog/?series=werken-met-django">Werken met Django</a> is een nieuwe tutorial beschikbaar: <a href="https://python-cursus.nl/blog/django-models/">Werken met een database in Django</a>.</p><p>Django biedt een krachtig systeem om gegevens in een database op te slaan en te beheren via <em>models</em>. In deze tutorial leer je hoe je <em>models</em> definieert, migraties uitvoert om je database structuur te cre&#235;ren, en de Django Admin gebruikt om gegevens toe te voegen. Je leert ook hoe je met <em>queries</em> data uit je database haalt om in je templates te tonen.</p><p>Ben je ge&#239;nteresseerd in het maken van een webapplicatie in Python, bekijk de reeks dan zeker!</p><div><hr></div><p class="button-wrapper" data-attrs="{&quot;url&quot;:&quot;https://python-cursus.nl/blog/django-models/&quot;,&quot;text&quot;:&quot;Naar Werken met een database in Django&quot;,&quot;action&quot;:null,&quot;class&quot;:null}" data-component-name="ButtonCreateButton"><a class="button primary" href="https://python-cursus.nl/blog/django-models/"><span>Naar Werken met een database in Django</span></a></p><p class="button-wrapper" data-attrs="{&quot;url&quot;:&quot;https://python-cursus.nl/blog/?series=werken-met-django&quot;,&quot;text&quot;:&quot;Naar de reeks&quot;,&quot;action&quot;:null,&quot;class&quot;:null}" data-component-name="ButtonCreateButton"><a class="button primary" href="https://python-cursus.nl/blog/?series=werken-met-django"><span>Naar de reeks</span></a></p><p></p>]]></content:encoded></item><item><title><![CDATA[Werken met JSON]]></title><description><![CDATA[Veel gegevens die je via het web kunt verkrijgen, zijn in JSON verkrijgbaar.]]></description><link>https://pythoncursus.substack.com/p/werken-met-json-f64</link><guid isPermaLink="false">https://pythoncursus.substack.com/p/werken-met-json-f64</guid><dc:creator><![CDATA[python-cursus.nl]]></dc:creator><pubDate>Mon, 03 Mar 2025 09:53:45 GMT</pubDate><enclosure url="https://substack-post-media.s3.amazonaws.com/public/images/4bbe7c2f-33cd-4a15-9b1a-42d134321eda_1024x1024.webp" length="0" type="image/jpeg"/><content:encoded><![CDATA[<p>Veel gegevens die je via het web kunt verkrijgen, zijn in <a href="https://nl.wikipedia.org/wiki/JSON">JSON</a> verkrijgbaar. Dit geldt zeker als je met API&#8217;s werkt. Een API is set afspraken waarmee verschillende programma's met elkaar kunnen praten. Zo kun je bijvoorbeeld aan het CBS vragen: &#8220;Geef mij alle bevolkingsgegevens&#8221; via de daarvoor bedoelde URL. Het CBS zal je die data dan geven in het JSON-formaat.</p><p>Een voorbeeld van een (klein) JSON-bestandje is:</p><pre><code><code>[
   {
      "Naam":"JSON",
      "Type":"Gegevensuitwisselingsformaat",
      "isProgrammeertaal":false,
      "Zie ook":[
         "XML",
         "ASN.1"
      ]
   },
   {
      "Naam":"JavaScript",
      "Type":"Programmeertaal",
      "isProgrammeertaal":true,
      "Jaar":1995
   }
]</code></code></pre><p>Je ziet: dit lijkt erg op een lijst met twee dictionaries! Het voordeel van JSON is dat het zowel door computers goed leesbaar is, maar voor mensen ook. </p><p>Op python-cursus.nl vind je de reeks <a href="https://python-cursus.nl/blog/?series=werken-met-json">Werken met JSON</a>. Werk je met API&#8217;s of wil je dit gaan doen, dan is het zeker de moeite waard om deze reeks eens te bekijken!</p><p class="button-wrapper" data-attrs="{&quot;url&quot;:&quot;https://python-cursus.nl/blog/?series=werken-met-json&quot;,&quot;text&quot;:&quot;Lees Werken met JSON&quot;,&quot;action&quot;:null,&quot;class&quot;:null}" data-component-name="ButtonCreateButton"><a class="button primary" href="https://python-cursus.nl/blog/?series=werken-met-json"><span>Lees Werken met JSON</span></a></p><p>Succes!</p>]]></content:encoded></item><item><title><![CDATA[Nieuwe reeks: Versiebeheer met Git]]></title><description><![CDATA[Samenwerken aan code wordt een stuk eenvoudiger met goed versiebeheer. In deze tutorial leer je de basis van Git, het meest gebruikte versiebeheersysteem voor softwareprojecten. Je leert hoe je wijzigingen bijhoudt, experimenteert met nieuwe functies en effectief samenwerkt met anderen - zonder dat bestanden kwijtraken of door elkaar lopen.]]></description><link>https://pythoncursus.substack.com/p/nieuwe-reeks-versiebeheer-met-git</link><guid isPermaLink="false">https://pythoncursus.substack.com/p/nieuwe-reeks-versiebeheer-met-git</guid><dc:creator><![CDATA[python-cursus.nl]]></dc:creator><pubDate>Fri, 07 Feb 2025 08:26:05 GMT</pubDate><enclosure url="https://substack-post-media.s3.amazonaws.com/public/images/65ecc02d-b737-48ca-a041-6b9264b98507_1024x1024.webp" length="0" type="image/jpeg"/><content:encoded><![CDATA[<p>Werken aan een softwareproject betekent: experimenteren, nieuwe dingen toevoegen, fouten oplossen. En vaak doe je dit niet alleen, maar bijvoorbeeld in een team.</p><p>Hoe zorg je er dan voor dat je op een veilige manier kunt experimenteren? Of hoe zorg je ervoor dat alle collega's aan dezelfde code kunnen werken, zonder elkaar in de weg te zitten? En hoe zorg je er vervolgens voor dat alle wijzigingen weer worden samengevoegd?</p><div class="captioned-image-container"><figure><a class="image-link image2 is-viewable-img" target="_blank" href="https://substackcdn.com/image/fetch/$s_!3Fp6!,f_auto,q_auto:good,fl_progressive:steep/https%3A%2F%2Fsubstack-post-media.s3.amazonaws.com%2Fpublic%2Fimages%2Ffdd50cab-c7ab-4fce-9e47-03043d9b8b58_256x256.webp" data-component-name="Image2ToDOM"><div class="image2-inset"><picture><source type="image/webp" srcset="https://substackcdn.com/image/fetch/$s_!3Fp6!,w_424,c_limit,f_webp,q_auto:good,fl_progressive:steep/https%3A%2F%2Fsubstack-post-media.s3.amazonaws.com%2Fpublic%2Fimages%2Ffdd50cab-c7ab-4fce-9e47-03043d9b8b58_256x256.webp 424w, https://substackcdn.com/image/fetch/$s_!3Fp6!,w_848,c_limit,f_webp,q_auto:good,fl_progressive:steep/https%3A%2F%2Fsubstack-post-media.s3.amazonaws.com%2Fpublic%2Fimages%2Ffdd50cab-c7ab-4fce-9e47-03043d9b8b58_256x256.webp 848w, https://substackcdn.com/image/fetch/$s_!3Fp6!,w_1272,c_limit,f_webp,q_auto:good,fl_progressive:steep/https%3A%2F%2Fsubstack-post-media.s3.amazonaws.com%2Fpublic%2Fimages%2Ffdd50cab-c7ab-4fce-9e47-03043d9b8b58_256x256.webp 1272w, https://substackcdn.com/image/fetch/$s_!3Fp6!,w_1456,c_limit,f_webp,q_auto:good,fl_progressive:steep/https%3A%2F%2Fsubstack-post-media.s3.amazonaws.com%2Fpublic%2Fimages%2Ffdd50cab-c7ab-4fce-9e47-03043d9b8b58_256x256.webp 1456w" sizes="100vw"><img src="https://substackcdn.com/image/fetch/$s_!3Fp6!,w_1456,c_limit,f_auto,q_auto:good,fl_progressive:steep/https%3A%2F%2Fsubstack-post-media.s3.amazonaws.com%2Fpublic%2Fimages%2Ffdd50cab-c7ab-4fce-9e47-03043d9b8b58_256x256.webp" width="256" height="256" data-attrs="{&quot;src&quot;:&quot;https://substack-post-media.s3.amazonaws.com/public/images/fdd50cab-c7ab-4fce-9e47-03043d9b8b58_256x256.webp&quot;,&quot;srcNoWatermark&quot;:null,&quot;fullscreen&quot;:null,&quot;imageSize&quot;:null,&quot;height&quot;:256,&quot;width&quot;:256,&quot;resizeWidth&quot;:null,&quot;bytes&quot;:9976,&quot;alt&quot;:null,&quot;title&quot;:null,&quot;type&quot;:&quot;image/webp&quot;,&quot;href&quot;:null,&quot;belowTheFold&quot;:false,&quot;topImage&quot;:true,&quot;internalRedirect&quot;:null,&quot;isProcessing&quot;:false,&quot;align&quot;:null,&quot;offset&quot;:false}" class="sizing-normal" alt="" srcset="https://substackcdn.com/image/fetch/$s_!3Fp6!,w_424,c_limit,f_auto,q_auto:good,fl_progressive:steep/https%3A%2F%2Fsubstack-post-media.s3.amazonaws.com%2Fpublic%2Fimages%2Ffdd50cab-c7ab-4fce-9e47-03043d9b8b58_256x256.webp 424w, https://substackcdn.com/image/fetch/$s_!3Fp6!,w_848,c_limit,f_auto,q_auto:good,fl_progressive:steep/https%3A%2F%2Fsubstack-post-media.s3.amazonaws.com%2Fpublic%2Fimages%2Ffdd50cab-c7ab-4fce-9e47-03043d9b8b58_256x256.webp 848w, https://substackcdn.com/image/fetch/$s_!3Fp6!,w_1272,c_limit,f_auto,q_auto:good,fl_progressive:steep/https%3A%2F%2Fsubstack-post-media.s3.amazonaws.com%2Fpublic%2Fimages%2Ffdd50cab-c7ab-4fce-9e47-03043d9b8b58_256x256.webp 1272w, https://substackcdn.com/image/fetch/$s_!3Fp6!,w_1456,c_limit,f_auto,q_auto:good,fl_progressive:steep/https%3A%2F%2Fsubstack-post-media.s3.amazonaws.com%2Fpublic%2Fimages%2Ffdd50cab-c7ab-4fce-9e47-03043d9b8b58_256x256.webp 1456w" sizes="100vw" fetchpriority="high"></picture><div class="image-link-expand"><div class="pencraft pc-display-flex pc-gap-8 pc-reset"><button tabindex="0" type="button" class="pencraft pc-reset pencraft icon-container restack-image"><svg aria-hidden="true" width="20" height="20" viewBox="0 0 20 20" fill="none" stroke-width="1.5" stroke="var(--color-fg-primary)" stroke-linecap="round" stroke-linejoin="round" xmlns="http://www.w3.org/2000/svg"><g><path d="M2.53001 7.81595C3.49179 4.73911 6.43281 2.5 9.91173 2.5C13.1684 2.5 15.9537 4.46214 17.0852 7.23684L17.6179 8.67647M17.6179 8.67647L18.5002 4.26471M17.6179 8.67647L13.6473 6.91176M17.4995 12.1841C16.5378 15.2609 13.5967 17.5 10.1178 17.5C6.86118 17.5 4.07589 15.5379 2.94432 12.7632L2.41165 11.3235M2.41165 11.3235L1.5293 15.7353M2.41165 11.3235L6.38224 13.0882"></path></g></svg></button><button tabindex="0" type="button" class="pencraft pc-reset pencraft icon-container view-image"><svg xmlns="http://www.w3.org/2000/svg" width="20" height="20" viewBox="0 0 24 24" fill="none" stroke="currentColor" stroke-width="2" stroke-linecap="round" stroke-linejoin="round" class="lucide lucide-maximize2 lucide-maximize-2"><polyline points="15 3 21 3 21 9"></polyline><polyline points="9 21 3 21 3 15"></polyline><line x1="21" x2="14" y1="3" y2="10"></line><line x1="3" x2="10" y1="21" y2="14"></line></svg></button></div></div></div></a></figure></div><p>Daar komt <strong>versiebeheer</strong> om de hoek kijken. Het meestgebruikte versiebeheersysteem in de softwareontwikkeling is <strong>Git</strong>. In deze nieuwe reeks leer je de basis van het werken met Git, inclusief het werken in verschillende <em>branches </em>en hoe je jouw werk op een centrale plek kunt opslaan, zodat je collega&#8217;s er ook bij kunnen.</p><p class="button-wrapper" data-attrs="{&quot;url&quot;:&quot;https://python-cursus.nl/blog/?series=werken-met-git&quot;,&quot;text&quot;:&quot;Bekijk de reeks 'Werken met Git'&quot;,&quot;action&quot;:null,&quot;class&quot;:null}" data-component-name="ButtonCreateButton"><a class="button primary" href="https://python-cursus.nl/blog/?series=werken-met-git"><span>Bekijk de reeks 'Werken met Git'</span></a></p><p></p>]]></content:encoded></item><item><title><![CDATA[Context toevoegen aan Django templates]]></title><description><![CDATA[Leer hoe je met Django's context systeem dynamische data doorgeeft aan templates en deze verwerkt met template tags en filters voor interactieve pagina's.]]></description><link>https://pythoncursus.substack.com/p/context-toevoegen-aan-django-templates</link><guid isPermaLink="false">https://pythoncursus.substack.com/p/context-toevoegen-aan-django-templates</guid><dc:creator><![CDATA[python-cursus.nl]]></dc:creator><pubDate>Sun, 05 Jan 2025 15:25:29 GMT</pubDate><enclosure url="https://substack-post-media.s3.amazonaws.com/public/images/6fac2ec3-151e-483d-90a9-ad2667cd7f82_1024x1024.webp" length="0" type="image/jpeg"/><content:encoded><![CDATA[<blockquote><p><a href="https://python-cursus.nl/blog/django-context/">Lees deze tutorial ook op python-cursus.nl</a></p></blockquote><p>In de <a href="https://python-cursus.nl/blog/django-templates/">vorige tutorial</a> leerde je over de basis van templates in Django. In deze tutorial ga je een stap verder en ga je <strong>context</strong> toevoegen. De context is een <code>dict</code> die je mee kunt geven aan een template. Hiermee kun je allerlei gegevens beschikbaar maken in je template, bijvoorbeeld een lijst om over te itereren.</p><p>Aan het eind van deze tutorial heb je de <code>overview</code>-template uit de vorige tutorial uitgebreid met een lijst met auto's die je (al dan niet fictief) in je bezit hebt, en met je naam. Dat ziet er ongeveer zo uit:</p><div class="captioned-image-container"><figure><a class="image-link image2 is-viewable-img" target="_blank" href="https://substackcdn.com/image/fetch/$s_!gdg0!,f_auto,q_auto:good,fl_progressive:steep/https%3A%2F%2Fsubstack-post-media.s3.amazonaws.com%2Fpublic%2Fimages%2F5af59637-5669-4b83-be78-977a530f6999_2638x998.webp" data-component-name="Image2ToDOM"><div class="image2-inset"><picture><source type="image/webp" srcset="https://substackcdn.com/image/fetch/$s_!gdg0!,w_424,c_limit,f_webp,q_auto:good,fl_progressive:steep/https%3A%2F%2Fsubstack-post-media.s3.amazonaws.com%2Fpublic%2Fimages%2F5af59637-5669-4b83-be78-977a530f6999_2638x998.webp 424w, https://substackcdn.com/image/fetch/$s_!gdg0!,w_848,c_limit,f_webp,q_auto:good,fl_progressive:steep/https%3A%2F%2Fsubstack-post-media.s3.amazonaws.com%2Fpublic%2Fimages%2F5af59637-5669-4b83-be78-977a530f6999_2638x998.webp 848w, https://substackcdn.com/image/fetch/$s_!gdg0!,w_1272,c_limit,f_webp,q_auto:good,fl_progressive:steep/https%3A%2F%2Fsubstack-post-media.s3.amazonaws.com%2Fpublic%2Fimages%2F5af59637-5669-4b83-be78-977a530f6999_2638x998.webp 1272w, https://substackcdn.com/image/fetch/$s_!gdg0!,w_1456,c_limit,f_webp,q_auto:good,fl_progressive:steep/https%3A%2F%2Fsubstack-post-media.s3.amazonaws.com%2Fpublic%2Fimages%2F5af59637-5669-4b83-be78-977a530f6999_2638x998.webp 1456w" sizes="100vw"><img src="https://substackcdn.com/image/fetch/$s_!gdg0!,w_1456,c_limit,f_auto,q_auto:good,fl_progressive:steep/https%3A%2F%2Fsubstack-post-media.s3.amazonaws.com%2Fpublic%2Fimages%2F5af59637-5669-4b83-be78-977a530f6999_2638x998.webp" width="1456" height="551" data-attrs="{&quot;src&quot;:&quot;https://substack-post-media.s3.amazonaws.com/public/images/5af59637-5669-4b83-be78-977a530f6999_2638x998.webp&quot;,&quot;srcNoWatermark&quot;:null,&quot;fullscreen&quot;:null,&quot;imageSize&quot;:null,&quot;height&quot;:551,&quot;width&quot;:1456,&quot;resizeWidth&quot;:null,&quot;bytes&quot;:null,&quot;alt&quot;:&quot;Het eindresultaat van deze tutorial&quot;,&quot;title&quot;:null,&quot;type&quot;:null,&quot;href&quot;:null,&quot;belowTheFold&quot;:false,&quot;topImage&quot;:true,&quot;internalRedirect&quot;:null,&quot;isProcessing&quot;:false,&quot;align&quot;:null,&quot;offset&quot;:false}" class="sizing-normal" alt="Het eindresultaat van deze tutorial" title="Het eindresultaat van deze tutorial" srcset="https://substackcdn.com/image/fetch/$s_!gdg0!,w_424,c_limit,f_auto,q_auto:good,fl_progressive:steep/https%3A%2F%2Fsubstack-post-media.s3.amazonaws.com%2Fpublic%2Fimages%2F5af59637-5669-4b83-be78-977a530f6999_2638x998.webp 424w, https://substackcdn.com/image/fetch/$s_!gdg0!,w_848,c_limit,f_auto,q_auto:good,fl_progressive:steep/https%3A%2F%2Fsubstack-post-media.s3.amazonaws.com%2Fpublic%2Fimages%2F5af59637-5669-4b83-be78-977a530f6999_2638x998.webp 848w, https://substackcdn.com/image/fetch/$s_!gdg0!,w_1272,c_limit,f_auto,q_auto:good,fl_progressive:steep/https%3A%2F%2Fsubstack-post-media.s3.amazonaws.com%2Fpublic%2Fimages%2F5af59637-5669-4b83-be78-977a530f6999_2638x998.webp 1272w, https://substackcdn.com/image/fetch/$s_!gdg0!,w_1456,c_limit,f_auto,q_auto:good,fl_progressive:steep/https%3A%2F%2Fsubstack-post-media.s3.amazonaws.com%2Fpublic%2Fimages%2F5af59637-5669-4b83-be78-977a530f6999_2638x998.webp 1456w" sizes="100vw" fetchpriority="high"></picture><div class="image-link-expand"><div class="pencraft pc-display-flex pc-gap-8 pc-reset"><button tabindex="0" type="button" class="pencraft pc-reset pencraft icon-container restack-image"><svg aria-hidden="true" width="20" height="20" viewBox="0 0 20 20" fill="none" stroke-width="1.5" stroke="var(--color-fg-primary)" stroke-linecap="round" stroke-linejoin="round" xmlns="http://www.w3.org/2000/svg"><g><path d="M2.53001 7.81595C3.49179 4.73911 6.43281 2.5 9.91173 2.5C13.1684 2.5 15.9537 4.46214 17.0852 7.23684L17.6179 8.67647M17.6179 8.67647L18.5002 4.26471M17.6179 8.67647L13.6473 6.91176M17.4995 12.1841C16.5378 15.2609 13.5967 17.5 10.1178 17.5C6.86118 17.5 4.07589 15.5379 2.94432 12.7632L2.41165 11.3235M2.41165 11.3235L1.5293 15.7353M2.41165 11.3235L6.38224 13.0882"></path></g></svg></button><button tabindex="0" type="button" class="pencraft pc-reset pencraft icon-container view-image"><svg xmlns="http://www.w3.org/2000/svg" width="20" height="20" viewBox="0 0 24 24" fill="none" stroke="currentColor" stroke-width="2" stroke-linecap="round" stroke-linejoin="round" class="lucide lucide-maximize2 lucide-maximize-2"><polyline points="15 3 21 3 21 9"></polyline><polyline points="9 21 3 21 3 15"></polyline><line x1="21" x2="14" y1="3" y2="10"></line><line x1="3" x2="10" y1="21" y2="14"></line></svg></button></div></div></div></a></figure></div><p>Open je <code>autotracker</code>-project en activeer de <code>venv</code>.</p><pre><code><code>cd ~/projects/autotracker
source .venv/bin/activate  # .venv\Scripts\activate.bat voor Windows
</code></code></pre><h2>Auto objecten maken</h2><p>Een <code>context</code> is in de context (ha ha) van templates een <code>dict</code> met objecten. Dat kan eigenlijk van alles zijn, en het hangt helemaal van je applicatie af wat een logische context is. In het geval van AutoTracker is een belangrijk object natuurlijk de auto. Om de <code>context</code> te demonstreren, ga je de <em>overview</em>-pagina uitbreiden met auto's die je momenteel in je bezit hebt.</p><p>Dat ga je eerst nog op een statische manier doen. Later in deze reeks zul je gaan werken met een database waarin je auto's zijn opgeslagen. Maar voor nu volstaat het om enkele objecten handmatig aan te maken.</p><p>Dat kan op verschillende manieren. Gebruik voor nu een <code>dataclass</code>. Als je nog niet bekend met <code>dataclasses</code>, een <code>dataclass</code> is een speciale klasse, waarmee je op vereenvoudigde wijze gegevens kunt opslaan. Ideaal dus als je vooral met gegevens wilt werken, en niet zozeer met logica.</p><p>Een <code>dataclass</code> voor een auto maak je als volgt aan:</p><pre><code><code>from dataclasses import dataclass

@dataclass
class Car:
    name: str
    purchase_price: int
    purchase_year: int
    construction_year: int
    fuel_type: str
    km_at_purchase: int
</code></code></pre><p>Je ziet dat je klasse-attributen en <a href="https://python-cursus.nl/blog/typesysteem-en-type-hints/">type hints</a> gebruikt om een <code>dataclass</code> op te bouwen. Het is een verkorte manier voor:</p><pre><code><code>class Car:
   def __init__(self, name: str, purchase_price: int, purchase_year: int, 
                construction_year: int, fuel_type: str, km_at_purchase: int):
       self.name = name
       self.purchase_price = purchase_price 
       self.purchase_year = purchase_year
       self.construction_year = construction_year
       self.fuel_type = fuel_type
       self.km_at_purchase = km_at_purchase
</code></code></pre><blockquote><p>In de <a href="https://basis.python-cursus.nl/werken-met-klassen/">Basiscursus Python</a> lees je nog eens over het werken met klassen. In <a href="https://python-cursus.nl/blog/typesysteem-en-type-hints/">Werken met Pythons Typesysteem en Type Hints</a> lees je nog eens over het werken met types.</p></blockquote><p>Plaats de <code>Car</code>-klasse in het bestand <code>models.py</code>, in de <code>tracker</code>-map. In latere tutorials leer je wat 'echte' <code>models</code> zijn en hoe je ermee werkt, voor nu volstaat deze <code>dataclass</code>.</p><p>De volgende stap is om een aantal auto's te maken. Je kunt je eigen auto('s) invoeren, maar je kunt er ook een paar bedenken. Maak er in elk geval meer dan &#233;&#233;n. Doe dit in de functie <code>overview</code> in <code>tracker/views.py</code>. Vergeet niet de <code>Car</code>-klasse te importeren.</p><pre><code><code>from tracker.models import Car

def overview(request):

    leaf = Car(
        name="Nissan Leaf",
        purchase_price=12000,
        purchase_year=2024,
        construction_year=2018,
        fuel_type="Electric",
        km_at_purchase=90000
    )

    ford = Car(
        name="Ford Focus Wagon",
        purchase_price=10000,
        purchase_year=2023,
        construction_year=2018,
        fuel_type="Gas",
        km_at_purchase=120000
    )


    return render(request, "overview.html")
</code></code></pre><h2>Auto's aan de context toevoegen</h2><p>Je hebt nu een aantal auto's aangemaakt in de <code>overview</code>-functie, maar hiermee zijn ze nog niet beschikbaar in je templates. Daarvoor voeg je ze eerst toe als <code>context</code>. Een <code>context</code> is een eenvoudige <code>dict</code> met objecten, in dit geval de auto's. Het toevoegen van de <code>context</code> doe je in twee stappen.</p><p>Maak eerst een <code>dict</code> genaamd <code>context</code> aan, met als sleutel "cars" en als waarden een lijst met je zojuist aangemaakte auto's. Je ho&#233;ft deze <code>dict</code> overigens geen <code>context</code> te noemen, maar het is wel zo duidelijk om dat wel te doen.</p><p>Vervolgens geef je deze <code>dict</code> mee aan de <code>render</code>-functie.</p><pre><code><code>from tracker.models import Car

def overview(request):

    leaf = Car(
        ...
    )

    ford = Car(
        ...
    )

    context = {
        "cars": [leaf, ford]
    }

    return render(request, "overview.html", context=context)
</code></code></pre><p>En dat is het! In je template is "cars" nu beschikbaar als variabele. Hoe dat werkt, zie je zo. Om te laten zien dat je van alles aan een context kunt toevoegen, kun je de <code>dict</code> nog met iets (willekeurigs) uitbreiden. Bijvoorbeeld je naam:</p><pre><code><code>context = {
    "cars": [leaf, ford],
    "name": "Erwin Matijsen"
}
</code></code></pre><h2>De context in een template gebruiken</h2><p>Je hebt nu een context beschikbaar gemaakt aan je template. De sleutels van je <code>dict</code> worden beschikbaar als variabelen in je template. In dit geval heb je dus twee variabelen tot je beschikking in je <code>overview</code>-template: <code>cars</code> en <code>name</code>. Maar hoe gebruik je deze variabelen nu in je template?</p><h3>Variabelen</h3><p>Dit doe je op de eenvoudigste manier met de syntax voor variabelen: <code>{{ &lt;variabele&gt; }}</code>. Voeg aan <code>overview.html</code> bijvoorbeeld het volgende toe:</p><pre><code><code>{% extends "base.html" %}

{% block main %}
    &lt;h1&gt;Overzicht&lt;/h1&gt;
    &lt;hr/&gt;

    &lt;p&gt;Hallo, {{ name }}. Dit zijn je auto's: &lt;/p&gt;
    &lt;p&gt;{{ cars }}&lt;/p&gt;

{% endblock main %}
</code></code></pre><p>Je ziet dat je <code>{{ name }}</code> en <code>{{ cars }}</code> kunt gebruiken. Herlaad de pagina, en je zult zien dat je de waarde ziet die je aan de <code>context</code> hebt toegevoegd met de sleutel <code>name</code>. Daar waar je <code>{{ cars }}</code> hebt toegevoegd, zie je een (niet zo mooie) lijst met je <code>Car</code>-objecten.</p><div class="captioned-image-container"><figure><a class="image-link image2 is-viewable-img" target="_blank" href="https://substackcdn.com/image/fetch/$s_!KZry!,f_auto,q_auto:good,fl_progressive:steep/https%3A%2F%2Fsubstack-post-media.s3.amazonaws.com%2Fpublic%2Fimages%2F2142adc0-3f4c-430b-a3a9-68f691891b7e_2638x998.webp" data-component-name="Image2ToDOM"><div class="image2-inset"><picture><source type="image/webp" srcset="https://substackcdn.com/image/fetch/$s_!KZry!,w_424,c_limit,f_webp,q_auto:good,fl_progressive:steep/https%3A%2F%2Fsubstack-post-media.s3.amazonaws.com%2Fpublic%2Fimages%2F2142adc0-3f4c-430b-a3a9-68f691891b7e_2638x998.webp 424w, https://substackcdn.com/image/fetch/$s_!KZry!,w_848,c_limit,f_webp,q_auto:good,fl_progressive:steep/https%3A%2F%2Fsubstack-post-media.s3.amazonaws.com%2Fpublic%2Fimages%2F2142adc0-3f4c-430b-a3a9-68f691891b7e_2638x998.webp 848w, https://substackcdn.com/image/fetch/$s_!KZry!,w_1272,c_limit,f_webp,q_auto:good,fl_progressive:steep/https%3A%2F%2Fsubstack-post-media.s3.amazonaws.com%2Fpublic%2Fimages%2F2142adc0-3f4c-430b-a3a9-68f691891b7e_2638x998.webp 1272w, https://substackcdn.com/image/fetch/$s_!KZry!,w_1456,c_limit,f_webp,q_auto:good,fl_progressive:steep/https%3A%2F%2Fsubstack-post-media.s3.amazonaws.com%2Fpublic%2Fimages%2F2142adc0-3f4c-430b-a3a9-68f691891b7e_2638x998.webp 1456w" sizes="100vw"><img src="https://substackcdn.com/image/fetch/$s_!KZry!,w_1456,c_limit,f_auto,q_auto:good,fl_progressive:steep/https%3A%2F%2Fsubstack-post-media.s3.amazonaws.com%2Fpublic%2Fimages%2F2142adc0-3f4c-430b-a3a9-68f691891b7e_2638x998.webp" width="1456" height="551" data-attrs="{&quot;src&quot;:&quot;https://substack-post-media.s3.amazonaws.com/public/images/2142adc0-3f4c-430b-a3a9-68f691891b7e_2638x998.webp&quot;,&quot;srcNoWatermark&quot;:null,&quot;fullscreen&quot;:null,&quot;imageSize&quot;:null,&quot;height&quot;:551,&quot;width&quot;:1456,&quot;resizeWidth&quot;:null,&quot;bytes&quot;:null,&quot;alt&quot;:&quot;Gebruik van variabelen in een Django Template&quot;,&quot;title&quot;:null,&quot;type&quot;:null,&quot;href&quot;:null,&quot;belowTheFold&quot;:true,&quot;topImage&quot;:false,&quot;internalRedirect&quot;:null,&quot;isProcessing&quot;:false,&quot;align&quot;:null,&quot;offset&quot;:false}" class="sizing-normal" alt="Gebruik van variabelen in een Django Template" title="Gebruik van variabelen in een Django Template" srcset="https://substackcdn.com/image/fetch/$s_!KZry!,w_424,c_limit,f_auto,q_auto:good,fl_progressive:steep/https%3A%2F%2Fsubstack-post-media.s3.amazonaws.com%2Fpublic%2Fimages%2F2142adc0-3f4c-430b-a3a9-68f691891b7e_2638x998.webp 424w, https://substackcdn.com/image/fetch/$s_!KZry!,w_848,c_limit,f_auto,q_auto:good,fl_progressive:steep/https%3A%2F%2Fsubstack-post-media.s3.amazonaws.com%2Fpublic%2Fimages%2F2142adc0-3f4c-430b-a3a9-68f691891b7e_2638x998.webp 848w, https://substackcdn.com/image/fetch/$s_!KZry!,w_1272,c_limit,f_auto,q_auto:good,fl_progressive:steep/https%3A%2F%2Fsubstack-post-media.s3.amazonaws.com%2Fpublic%2Fimages%2F2142adc0-3f4c-430b-a3a9-68f691891b7e_2638x998.webp 1272w, https://substackcdn.com/image/fetch/$s_!KZry!,w_1456,c_limit,f_auto,q_auto:good,fl_progressive:steep/https%3A%2F%2Fsubstack-post-media.s3.amazonaws.com%2Fpublic%2Fimages%2F2142adc0-3f4c-430b-a3a9-68f691891b7e_2638x998.webp 1456w" sizes="100vw" loading="lazy"></picture><div class="image-link-expand"><div class="pencraft pc-display-flex pc-gap-8 pc-reset"><button tabindex="0" type="button" class="pencraft pc-reset pencraft icon-container restack-image"><svg aria-hidden="true" width="20" height="20" viewBox="0 0 20 20" fill="none" stroke-width="1.5" stroke="var(--color-fg-primary)" stroke-linecap="round" stroke-linejoin="round" xmlns="http://www.w3.org/2000/svg"><g><path d="M2.53001 7.81595C3.49179 4.73911 6.43281 2.5 9.91173 2.5C13.1684 2.5 15.9537 4.46214 17.0852 7.23684L17.6179 8.67647M17.6179 8.67647L18.5002 4.26471M17.6179 8.67647L13.6473 6.91176M17.4995 12.1841C16.5378 15.2609 13.5967 17.5 10.1178 17.5C6.86118 17.5 4.07589 15.5379 2.94432 12.7632L2.41165 11.3235M2.41165 11.3235L1.5293 15.7353M2.41165 11.3235L6.38224 13.0882"></path></g></svg></button><button tabindex="0" type="button" class="pencraft pc-reset pencraft icon-container view-image"><svg xmlns="http://www.w3.org/2000/svg" width="20" height="20" viewBox="0 0 24 24" fill="none" stroke="currentColor" stroke-width="2" stroke-linecap="round" stroke-linejoin="round" class="lucide lucide-maximize2 lucide-maximize-2"><polyline points="15 3 21 3 21 9"></polyline><polyline points="9 21 3 21 3 15"></polyline><line x1="21" x2="14" y1="3" y2="10"></line><line x1="3" x2="10" y1="21" y2="14"></line></svg></button></div></div></div></a></figure></div><h3>Template tags</h3><p>Dat werkt, maar is niet zo mooi natuurlijk. Het zou mooier zijn als je een nette lijst zag met de namen van de auto's en bijvoorbeeld het aanschafjaar erachter. Dit doe je met de <code>for</code>-<em>template tag</em>. <em>Template tags</em> heb je al eerder gebruikt, namelijk <code>{% extends "base.html" %}</code> en <code>{% block main %}</code>. Misschien herken je de syntax al voor de meeste <em>template tags</em>: <code>{% ... %}</code>.</p><p>De <code>for</code>-<em>template tag</em> is iets uitgebreider. Het werkt vergelijkbaar als een <code>for-loop</code> in Python. Verander je template naar het volgende:</p><pre><code><code>{% extends "base.html" %}

{% block main %}
    &lt;h1&gt;Overzicht&lt;/h1&gt;
    &lt;hr/&gt;

    &lt;p&gt;Hallo, {{ name }}. Dit zijn je auto's: &lt;/p&gt;

    &lt;ol&gt;
    {% for car in cars %}
        &lt;li&gt;{{ car.name }} ({{ car.purchase_year }})&lt;/li&gt;
    {% endfor %}
    &lt;/ol&gt;

{% endblock main %}
</code></code></pre><p>Je begint de <code>for-loop</code> met <code>{% for item in items %}</code> en eindigt het met <code>{% endfor %}</code>. Dit plaats je tussen de <code>&lt;ol&gt;</code>-tags. Voor elk item in de <code>cars</code>-lijst zul je nu een <code>&lt;li&gt;</code>-element toevoegen, met de naam van de auto en tussen haakjes het jaar van aanschaf. In elke <em>loop</em> heb je een variabele <code>car</code> tot je beschikking. Om deze aan te roepen, plaats je het weer tussen <code>{{</code> en <code>}}</code>. Omdat elke <code>car</code> een object van het type <code>Car</code> is, kun je de verschillende attributen aanroepen met de punt-notatie. Dus bijvoorbeeld <code>car.name</code> voor de naam en <code>car.purchase_year</code> voor het jaar van aanschaf.</p><p>Je kunt nog een kleine verbetering doorvoeren. Want wat als je (nog) geen auto's hebt toegevoegd? Hiervoor gebruik je de <code>{% if ... %}</code>-<em>template tag</em>.</p><pre><code><code>{% if cars %}
    &lt;ol&gt;
    {% for car in cars %}
        &lt;li&gt;{{ car.name }} ({{ car.purchase_year }})&lt;/li&gt;    
    {% endfor %}
    &lt;/ol&gt;
{% else %}
    &lt;p&gt;Je hebt nog geen auto's toegevoegd&lt;/p&gt;
{% endif %}
</code></code></pre><p>Hiermee toets je eerst of er een variabele <code>cars</code> is (of dat hij in elk geval niet leeg is). Als dit zo is, dan maak je de lijst met de <code>{% for ... %}</code>-constructie. Is er geen lijst met auto's, dan toon je een alternatieve tekst.</p><p>Maak de lijst in de <code>context</code>-<code>dict</code> maar eens leeg en kijk wat er in je template gebeurt als je de pagina herlaadt.</p><blockquote><p>Probeer als oefening eens de lijst in de template uit te breiden met andere kenmerken van de auto's, zoals de kilometerstand bij aanschaf. Om het overzichtelijk te maken zou je het in een tabel kunnen plaatsen. Lees op <a href="https://developer.mozilla.org/en-US/docs/Web/HTML/Element/table">MDN Web Docs</a> hoe je een HTML tabel maakt.</p></blockquote><h3>Template filters</h3><p>Naast <em>template tags</em> zijn er ook nog zogenaamde <em>template filters</em>. Met een <em>template filter</em> kun je waardes in een template wijzigen. Een simpel voorbeeld is de <code>upper</code>-filter, waarmee je een waarde omzet in kapitalen. Bijvoorbeeld de naam van je auto:</p><pre><code><code>...
&lt;li&gt;{{ car.name|upper }} ({{ car.purchase_year }})&lt;/li&gt;   
...
</code></code></pre><p>Je voegt <code>|upper</code> toe achter <code>car.name</code>. Een filter begint altijd met een <code>|</code>, gevolgd door de naam van het filter. Bij sommige filters is het ook nog mogelijk argumenten mee te geven, maar bij <code>upper</code> is dat niet het geval. Herlaad de pagina om te kijken of het werkt.</p><h2>Conclusie</h2><p>Je hebt gezien dat je op een eenvoudige manier je templates dynamisch kunt maken door een <code>context</code> toe te voegen. Een context is een <code>dict</code> met daarin items die je aan de template wilt toevoegen. Dit kan van alles zijn, van een eenvoudige <code>str</code> tot een uitgebreide lijst met complexe objecten.</p><p>Je roept een variabele in je template aan met <code>{{ &lt;variable&gt; }}</code>. Je kunt er ook over itereren met <code>{% for ... %}</code>. Dit zijn slechts de eerste stappen, met andere <em>template tags</em> en <em>template filters</em> is nog veel meer mogelijk. Maar de basis blijft gelijk. Zie de <a href="https://docs.djangoproject.com/en/5.1/ref/templates/builtins/">Django Documentatie</a> voor meer <em>template tags</em> en <em>template filters</em>.</p><blockquote><p><a href="https://python-cursus.nl/blog/django-context/">Lees deze tutorial ook op python-cursus.nl</a></p></blockquote>]]></content:encoded></item><item><title><![CDATA[2024 in review]]></title><description><![CDATA[Begin 2024 publiceerde ik het eerste bericht op python-cursus.nl. Nu, bijna een jaar later, kijk ik in een korte "2024 in review" terug op het eerste jaar!]]></description><link>https://pythoncursus.substack.com/p/2024-in-review</link><guid isPermaLink="false">https://pythoncursus.substack.com/p/2024-in-review</guid><dc:creator><![CDATA[python-cursus.nl]]></dc:creator><pubDate>Thu, 02 Jan 2025 13:10:46 GMT</pubDate><enclosure url="https://substack-post-media.s3.amazonaws.com/public/images/af7ee9fa-6438-41aa-bea8-ddc85ba4db0b_256x256.webp" length="0" type="image/jpeg"/><content:encoded><![CDATA[<p>In 2023 ontwikkelde ik materiaal ter begeleiding bij een Python-cursus. Aangezien ik het toch had liggen, leek het me leuk te publiceren. In plaats van het simpelweg online te zetten, bedacht ik dat er misschien wel interesse was voor een website rondom Python tutorials. Waarom, vraag je je misschien af. Er zijn immers al talloze tutorial-sites over Python te vinden op het internet, dus wat voegt n&#243;g een site toe?</p><p>Tussen al die al aanwezige sites had ik toch het idee dat er een bepaalde niche ontbrak, namelijk:</p><ul><li><p>Een site gericht op mensen die Python willen leren om hun werk eenvoudiger, effici&#235;nter of slimmer te maken, maar waarvoor programmeren niet hun hoofdtaak is.</p></li><li><p>En die Nederlandstalig is.</p></li></ul><p>Zo gezegd, zo gedaan. De basiscursus publiceerde ik op <a href="https://basis.python-cursus.nl/">basis.pythoncursus.nl</a> en op 15 januari 2024 publiceerde ik het <a href="https://python-cursus.nl/blog/ict-sector-en-python/">eerste bericht</a> op python-cursus.nl.</p><p>De tutorials die ik schreef zijn vooral gericht op zaken die je handig kunt gebruiken in je werk, zoals het <a href="https://python-cursus.nl/blog/?series=pandas">werken met pandas</a> voor data-analyse. Of het <a href="https://python-cursus.nl/blog/?series=werken-met-json">werken met JSON</a>, voor als je werkt met API's.</p><p>Inmiddels is 2024 voorbij en over een paar dagen is het precies een jaar geleden dat ik het eerste bericht plaatste. Een mooi moment om terug te blikken en te bepalen of python-cursus.nl inderdaad een bepaalde niche vult.</p><h3>2024</h3><p>Zoals de meeste sites houd ook ik de statistieken bij. Ik gebruik hiervoor <a href="https://www.simpleanalytics.com/nl">Simple Analytics</a>, een Nederlands alternatief voor Google Analytics met als onderscheidend kenmerk dat het privacy-vriendelijk is.</p><p>Een leuke <em>feature</em> is dat Simple Analytics ook een eigen API heeft, zodat je je eigen dashboards kunt maken. En dat is precies wat ik heb gedaan!</p><div class="captioned-image-container"><figure><a class="image-link image2 is-viewable-img" target="_blank" href="https://substackcdn.com/image/fetch/$s_!sFcl!,f_auto,q_auto:good,fl_progressive:steep/https%3A%2F%2Fsubstack-post-media.s3.amazonaws.com%2Fpublic%2Fimages%2F834e5c8e-83c0-4446-801a-72608f26bb5a_2896x1638.webp" data-component-name="Image2ToDOM"><div class="image2-inset"><picture><source type="image/webp" srcset="https://substackcdn.com/image/fetch/$s_!sFcl!,w_424,c_limit,f_webp,q_auto:good,fl_progressive:steep/https%3A%2F%2Fsubstack-post-media.s3.amazonaws.com%2Fpublic%2Fimages%2F834e5c8e-83c0-4446-801a-72608f26bb5a_2896x1638.webp 424w, https://substackcdn.com/image/fetch/$s_!sFcl!,w_848,c_limit,f_webp,q_auto:good,fl_progressive:steep/https%3A%2F%2Fsubstack-post-media.s3.amazonaws.com%2Fpublic%2Fimages%2F834e5c8e-83c0-4446-801a-72608f26bb5a_2896x1638.webp 848w, https://substackcdn.com/image/fetch/$s_!sFcl!,w_1272,c_limit,f_webp,q_auto:good,fl_progressive:steep/https%3A%2F%2Fsubstack-post-media.s3.amazonaws.com%2Fpublic%2Fimages%2F834e5c8e-83c0-4446-801a-72608f26bb5a_2896x1638.webp 1272w, https://substackcdn.com/image/fetch/$s_!sFcl!,w_1456,c_limit,f_webp,q_auto:good,fl_progressive:steep/https%3A%2F%2Fsubstack-post-media.s3.amazonaws.com%2Fpublic%2Fimages%2F834e5c8e-83c0-4446-801a-72608f26bb5a_2896x1638.webp 1456w" sizes="100vw"><img src="https://substackcdn.com/image/fetch/$s_!sFcl!,w_1456,c_limit,f_auto,q_auto:good,fl_progressive:steep/https%3A%2F%2Fsubstack-post-media.s3.amazonaws.com%2Fpublic%2Fimages%2F834e5c8e-83c0-4446-801a-72608f26bb5a_2896x1638.webp" width="1456" height="824" data-attrs="{&quot;src&quot;:&quot;https://substack-post-media.s3.amazonaws.com/public/images/834e5c8e-83c0-4446-801a-72608f26bb5a_2896x1638.webp&quot;,&quot;srcNoWatermark&quot;:null,&quot;fullscreen&quot;:null,&quot;imageSize&quot;:null,&quot;height&quot;:824,&quot;width&quot;:1456,&quot;resizeWidth&quot;:null,&quot;bytes&quot;:null,&quot;alt&quot;:&quot;Screenshot van stats.python-cursus.nl&quot;,&quot;title&quot;:null,&quot;type&quot;:null,&quot;href&quot;:null,&quot;belowTheFold&quot;:true,&quot;topImage&quot;:false,&quot;internalRedirect&quot;:null,&quot;isProcessing&quot;:false,&quot;align&quot;:null,&quot;offset&quot;:false}" class="sizing-normal" alt="Screenshot van stats.python-cursus.nl" title="Screenshot van stats.python-cursus.nl" srcset="https://substackcdn.com/image/fetch/$s_!sFcl!,w_424,c_limit,f_auto,q_auto:good,fl_progressive:steep/https%3A%2F%2Fsubstack-post-media.s3.amazonaws.com%2Fpublic%2Fimages%2F834e5c8e-83c0-4446-801a-72608f26bb5a_2896x1638.webp 424w, https://substackcdn.com/image/fetch/$s_!sFcl!,w_848,c_limit,f_auto,q_auto:good,fl_progressive:steep/https%3A%2F%2Fsubstack-post-media.s3.amazonaws.com%2Fpublic%2Fimages%2F834e5c8e-83c0-4446-801a-72608f26bb5a_2896x1638.webp 848w, https://substackcdn.com/image/fetch/$s_!sFcl!,w_1272,c_limit,f_auto,q_auto:good,fl_progressive:steep/https%3A%2F%2Fsubstack-post-media.s3.amazonaws.com%2Fpublic%2Fimages%2F834e5c8e-83c0-4446-801a-72608f26bb5a_2896x1638.webp 1272w, https://substackcdn.com/image/fetch/$s_!sFcl!,w_1456,c_limit,f_auto,q_auto:good,fl_progressive:steep/https%3A%2F%2Fsubstack-post-media.s3.amazonaws.com%2Fpublic%2Fimages%2F834e5c8e-83c0-4446-801a-72608f26bb5a_2896x1638.webp 1456w" sizes="100vw" loading="lazy"></picture><div class="image-link-expand"><div class="pencraft pc-display-flex pc-gap-8 pc-reset"><button tabindex="0" type="button" class="pencraft pc-reset pencraft icon-container restack-image"><svg aria-hidden="true" width="20" height="20" viewBox="0 0 20 20" fill="none" stroke-width="1.5" stroke="var(--color-fg-primary)" stroke-linecap="round" stroke-linejoin="round" xmlns="http://www.w3.org/2000/svg"><g><path d="M2.53001 7.81595C3.49179 4.73911 6.43281 2.5 9.91173 2.5C13.1684 2.5 15.9537 4.46214 17.0852 7.23684L17.6179 8.67647M17.6179 8.67647L18.5002 4.26471M17.6179 8.67647L13.6473 6.91176M17.4995 12.1841C16.5378 15.2609 13.5967 17.5 10.1178 17.5C6.86118 17.5 4.07589 15.5379 2.94432 12.7632L2.41165 11.3235M2.41165 11.3235L1.5293 15.7353M2.41165 11.3235L6.38224 13.0882"></path></g></svg></button><button tabindex="0" type="button" class="pencraft pc-reset pencraft icon-container view-image"><svg xmlns="http://www.w3.org/2000/svg" width="20" height="20" viewBox="0 0 24 24" fill="none" stroke="currentColor" stroke-width="2" stroke-linecap="round" stroke-linejoin="round" class="lucide lucide-maximize2 lucide-maximize-2"><polyline points="15 3 21 3 21 9"></polyline><polyline points="9 21 3 21 3 15"></polyline><line x1="21" x2="14" y1="3" y2="10"></line><line x1="3" x2="10" y1="21" y2="14"></line></svg></button></div></div></div></a></figure></div><p>Op <a href="https://stats.python-cursus.nl">stats.python-cursus.nl</a> kun je dit dashboard bekijken.</p><p>Het is een eenvoudige pagina, met een paar kernstatistieken over de website. Zo zie je dat er in 2024 bijna <strong>1200</strong> bezoekers waren, dat de tutorials over <code>pandas</code> populair zijn en dat de meeste bezoekers in het begin van de week (maandag t/m woensdag) komen. Misschien is dit laatste een (kleine) indicatie dat vooral mensen uit de beoogde doelgroep (werkenden) de site bezoeken...</p><p>Al met al vind ik de cijfers bemoedigend. Er is een niet-verwaarloosbaar aantal mensen dat python-cursus.nl weet te vinden en het nuttig vindt.</p><p>Iets dat me opviel is dat het bericht <a href="https://python-cursus.nl/blog/programmeren-overbodig-door-ai/">Moet ik nog leren programmeren met de opkomst van AI?</a> &#233;&#233;n van de populairdere berichten is. Misschien niet heel gek, gezien de stormachtige opkomst van AI. Maar op een site met vooral tutorials toch opvallend. Misschien is er wel behoefte aan dit soort meer reflecterende berichten?</p><h3>2025</h3><p>De statistieken tonen in elk geval aan dat er voldoende behoefte is aan een Nederlandstalige site over het werken met Python. In 2025 ga ik dan ook zeker door met het schrijven van tutorials voor python-cursus.nl. De focus blijft uiteraard gericht op praktische toepassingen van Python voor mensen die het in hun werk kunnen inzetten. Een aantal onderwerpen die ik wil behandelen zijn in elk geval:</p><ul><li><p>De reeks over <code>pandas</code> uitbreiden</p></li><li><p>Meer over werken met API's</p></li><li><p>Meer over (werken met) AI</p></li><li><p>De reeks over <code>Django</code> uitbreiden</p></li></ul><p>Heb je nog andere onderwerpen die je graag behandeld zou willen zien, laat het me dan vooral weten.</p><p class="button-wrapper" data-attrs="{&quot;url&quot;:&quot;https://python-cursus.nl/contact/&quot;,&quot;text&quot;:&quot;Neem contact op&quot;,&quot;action&quot;:null,&quot;class&quot;:null}" data-component-name="ButtonCreateButton"><a class="button primary" href="https://python-cursus.nl/contact/"><span>Neem contact op</span></a></p><p></p><p></p><p></p>]]></content:encoded></item><item><title><![CDATA[Gegevens groeperen met pandas]]></title><description><![CDATA[Pandas groupby is een krachtige functie voor het groeperen en analyseren van data in Python.]]></description><link>https://pythoncursus.substack.com/p/gegevens-groeperen-met-pandas</link><guid isPermaLink="false">https://pythoncursus.substack.com/p/gegevens-groeperen-met-pandas</guid><dc:creator><![CDATA[python-cursus.nl]]></dc:creator><pubDate>Thu, 12 Dec 2024 19:41:16 GMT</pubDate><enclosure url="https://substack-post-media.s3.amazonaws.com/public/images/ced686da-dbb7-4fe1-a698-97c37829fdff_256x256.webp" length="0" type="image/jpeg"/><content:encoded><![CDATA[<p>Pandas <code>groupby</code> is een krachtige functie voor het groeperen en analyseren van data in Python. Met <code>groupby</code> maak je analyses door gegevens te groeperen op basis van gemeenschappelijke waarden, een nuttige toevoeging aan je data-analyse toolset. Aan de hand van praktische voorbeelden met WOZ-waardes leer je hoe je <code>groupby</code>  kunt inzetten voor je eigen data-analyse.</p><p class="button-wrapper" data-attrs="{&quot;url&quot;:&quot;https://python-cursus.nl/blog/pandas-gegevens-groeperen/&quot;,&quot;text&quot;:&quot;Ga naar de tutorial&quot;,&quot;action&quot;:null,&quot;class&quot;:null}" data-component-name="ButtonCreateButton"><a class="button primary" href="https://python-cursus.nl/blog/pandas-gegevens-groeperen/"><span>Ga naar de tutorial</span></a></p>]]></content:encoded></item><item><title><![CDATA[Begin Django-reeks]]></title><description><![CDATA[Tutorials over het werken met het webframework Django]]></description><link>https://pythoncursus.substack.com/p/begin-django-reeks</link><guid isPermaLink="false">https://pythoncursus.substack.com/p/begin-django-reeks</guid><dc:creator><![CDATA[python-cursus.nl]]></dc:creator><pubDate>Thu, 10 Oct 2024 07:31:28 GMT</pubDate><enclosure url="https://substack-post-media.s3.amazonaws.com/public/images/a036a2e8-2806-4847-bd5f-0bde96baa207_1024x1024.webp" length="0" type="image/jpeg"/><content:encoded><![CDATA[<p>Op python-cursus.nl is een begin gemaakt met een reeks tutorials over Django. Django is een webframework voor Python, waarmee je webapplicaties kunt maken. Het is een zeer uitgebreid framework, zoals ze zelf zeggen: <em>batteries included</em>!<br><br>Om te beginnen met Django lees je <a href="https://python-cursus.nl/blog/django-introductie/">Webapplicaties maken met Django - Introductie</a>. In de vervolgtutorial <a href="https://python-cursus.nl/blog/django-templates/">Werken met templates in Django</a> leer je hoe je templates gebruikt om eenvoudig en zonder dubbel werk verschillende pagina&#8217;s kunt opbouwen. </p><p>De reeks zal in de toekomst nog worden uitgebouwd. Op de <a href="https://python-cursus.nl/blog/?series=werken-met-django">Series-pagina</a> zie je welke tutorials er in deze reeks beschikbaar zijn.</p><p>Veel plezier en succes!</p><p>PS: Fun fact: python-cursus.nl zelf is ook gebouwd met Django!</p>]]></content:encoded></item><item><title><![CDATA[Gegevens selecteren met pandas]]></title><description><![CDATA[Je zult vaak met een DataFrame werken als je met pandas werkt. Vaak bestaat zo'n DataFrame uit je gehele dataset, en heb je voor bepaalde analyses maar een subset nodig. In deze tutorial leer je daarom hoe je de juiste rijen en kolommen kunt selecteren om tot die subset te komen.]]></description><link>https://pythoncursus.substack.com/p/gegevens-selecteren-met-pandas</link><guid isPermaLink="false">https://pythoncursus.substack.com/p/gegevens-selecteren-met-pandas</guid><dc:creator><![CDATA[python-cursus.nl]]></dc:creator><pubDate>Mon, 08 Jul 2024 09:25:42 GMT</pubDate><enclosure url="https://substack-post-media.s3.amazonaws.com/public/images/0242ec84-ed39-43a8-a216-d1b125f389a7_1024x1024.webp" length="0" type="image/jpeg"/><content:encoded><![CDATA[<p>In <a href="https://python-cursus.nl/blog/pandas-gegevens-inlezen-en-opslaan/">Gegevens inlezen en opslaan met pandas</a> heb je geleerd hoe je gegevens vanuit verschillende typen bestanden - zoals CSV, JSON en Excel - kunt inlezen in <code>pandas</code>. In <code>pandas</code> werk je met de gegevens in een <code>DataFrame</code>, vergelijkbaar met een tabel. Lees die tutorial nog eens terug als je een opfrisser nodig hebt over wat een <code>DataFrame</code> ook alweer is.</p><p>In veel gevallen zul je niet met de hele <code>DataFrame</code> in &#233;&#233;n keer willen werken, maar wil je een selectie maken. Je hebt bijvoorbeeld maar enkele kolommen nodig, of juist alleen de rijen die voldoen aan een bepaalde voorwaarde. In deze tutorial leer je daarom hoe je gegevens uit een <code>DataFrame</code> kunt selecteren.</p><h2>Voordat je begint</h2><p>Om het selecteren te demonstreren, maak je gebruik van het project <a href="https://github.com/python-cursus-nl/knmi">KNMI</a>. Je kunt dit project bekijken op <a href="https://github.com/python-cursus-nl/knmi">Github</a>, zorg ervoor dat je voor deze tutorial de <em>branch</em> <code>versie-3</code> bekijkt.</p><pre><code><code>cd projects/knmi
source .venv/bin/activate  # .venv\Scripts\activate.bat voor Windows
git switch versie-3
python -m pip install --upgrade -r requirements
</code></code></pre><p>In het KNMI project download je data van het KNMI. Dit komt in een txt-bestand. Nadat je het tekstbestand hebt opgeslagen, schoon je het op en sla je het op als CSV-bestand, zodat er eenvoudiger mee is te werken. De data zijn maand- en jaargemiddelde temperaturen van het meetstation De Bilt.</p><p>Zorg ervoor dat je in <code>main.py</code> met de volgende code begint:</p><pre><code><code>from data import clean_data, download, load_data

if __name__ == "__main__":
    # Download de gegevens
    try:
        download()
    except requests.exceptions.HTTPError as e:
        print("Error while downloading file:", e)

    # Schoon de gegevens op, sla op als CSV
    clean_data()

    # Laad de gegevens in een pandas DataFrame
    df = load_data()
</code></code></pre><p>In <code>load_data()</code> is de volgende code te vinden:</p><pre><code><code>print(df.head(), "\n")
</code></code></pre><p>Hiermee toon je de eerste vijf rijen van de <code>DataFrame</code>, voor een snelle inspectie. Je kunt daar nog de volgende instructie aan toevoegen, voor een verdere inspectie:</p><pre><code><code>print(df.info(), "\n")
</code></code></pre><p>Dit toont alle aanwezige kolommen, per kolom het aantal rijen met een waarde (non-null) en het <code>dtype</code>. In dit geval zijn alle kolommen <code>int64</code> of <code>float64</code>.</p><div class="captioned-image-container"><figure><a class="image-link image2 is-viewable-img" target="_blank" href="https://substackcdn.com/image/fetch/$s_!2ddg!,f_auto,q_auto:good,fl_progressive:steep/https%3A%2F%2Fsubstack-post-media.s3.amazonaws.com%2Fpublic%2Fimages%2F1f80ddfb-2324-4450-a03f-37ec1c1c5948_475x596.png" data-component-name="Image2ToDOM"><div class="image2-inset"><picture><source type="image/webp" srcset="https://substackcdn.com/image/fetch/$s_!2ddg!,w_424,c_limit,f_webp,q_auto:good,fl_progressive:steep/https%3A%2F%2Fsubstack-post-media.s3.amazonaws.com%2Fpublic%2Fimages%2F1f80ddfb-2324-4450-a03f-37ec1c1c5948_475x596.png 424w, https://substackcdn.com/image/fetch/$s_!2ddg!,w_848,c_limit,f_webp,q_auto:good,fl_progressive:steep/https%3A%2F%2Fsubstack-post-media.s3.amazonaws.com%2Fpublic%2Fimages%2F1f80ddfb-2324-4450-a03f-37ec1c1c5948_475x596.png 848w, https://substackcdn.com/image/fetch/$s_!2ddg!,w_1272,c_limit,f_webp,q_auto:good,fl_progressive:steep/https%3A%2F%2Fsubstack-post-media.s3.amazonaws.com%2Fpublic%2Fimages%2F1f80ddfb-2324-4450-a03f-37ec1c1c5948_475x596.png 1272w, https://substackcdn.com/image/fetch/$s_!2ddg!,w_1456,c_limit,f_webp,q_auto:good,fl_progressive:steep/https%3A%2F%2Fsubstack-post-media.s3.amazonaws.com%2Fpublic%2Fimages%2F1f80ddfb-2324-4450-a03f-37ec1c1c5948_475x596.png 1456w" sizes="100vw"><img src="https://substackcdn.com/image/fetch/$s_!2ddg!,w_1456,c_limit,f_auto,q_auto:good,fl_progressive:steep/https%3A%2F%2Fsubstack-post-media.s3.amazonaws.com%2Fpublic%2Fimages%2F1f80ddfb-2324-4450-a03f-37ec1c1c5948_475x596.png" width="475" height="596" data-attrs="{&quot;src&quot;:&quot;https://substack-post-media.s3.amazonaws.com/public/images/1f80ddfb-2324-4450-a03f-37ec1c1c5948_475x596.png&quot;,&quot;srcNoWatermark&quot;:null,&quot;fullscreen&quot;:null,&quot;imageSize&quot;:null,&quot;height&quot;:596,&quot;width&quot;:475,&quot;resizeWidth&quot;:null,&quot;bytes&quot;:null,&quot;alt&quot;:&quot;Info over een DataFrame&quot;,&quot;title&quot;:null,&quot;type&quot;:null,&quot;href&quot;:null,&quot;belowTheFold&quot;:true,&quot;topImage&quot;:false,&quot;internalRedirect&quot;:null,&quot;isProcessing&quot;:false,&quot;align&quot;:null,&quot;offset&quot;:false}" class="sizing-normal" alt="Info over een DataFrame" title="Info over een DataFrame" srcset="https://substackcdn.com/image/fetch/$s_!2ddg!,w_424,c_limit,f_auto,q_auto:good,fl_progressive:steep/https%3A%2F%2Fsubstack-post-media.s3.amazonaws.com%2Fpublic%2Fimages%2F1f80ddfb-2324-4450-a03f-37ec1c1c5948_475x596.png 424w, https://substackcdn.com/image/fetch/$s_!2ddg!,w_848,c_limit,f_auto,q_auto:good,fl_progressive:steep/https%3A%2F%2Fsubstack-post-media.s3.amazonaws.com%2Fpublic%2Fimages%2F1f80ddfb-2324-4450-a03f-37ec1c1c5948_475x596.png 848w, https://substackcdn.com/image/fetch/$s_!2ddg!,w_1272,c_limit,f_auto,q_auto:good,fl_progressive:steep/https%3A%2F%2Fsubstack-post-media.s3.amazonaws.com%2Fpublic%2Fimages%2F1f80ddfb-2324-4450-a03f-37ec1c1c5948_475x596.png 1272w, https://substackcdn.com/image/fetch/$s_!2ddg!,w_1456,c_limit,f_auto,q_auto:good,fl_progressive:steep/https%3A%2F%2Fsubstack-post-media.s3.amazonaws.com%2Fpublic%2Fimages%2F1f80ddfb-2324-4450-a03f-37ec1c1c5948_475x596.png 1456w" sizes="100vw" loading="lazy"></picture><div class="image-link-expand"><div class="pencraft pc-display-flex pc-gap-8 pc-reset"><button tabindex="0" type="button" class="pencraft pc-reset pencraft icon-container restack-image"><svg aria-hidden="true" width="20" height="20" viewBox="0 0 20 20" fill="none" stroke-width="1.5" stroke="var(--color-fg-primary)" stroke-linecap="round" stroke-linejoin="round" xmlns="http://www.w3.org/2000/svg"><g><path d="M2.53001 7.81595C3.49179 4.73911 6.43281 2.5 9.91173 2.5C13.1684 2.5 15.9537 4.46214 17.0852 7.23684L17.6179 8.67647M17.6179 8.67647L18.5002 4.26471M17.6179 8.67647L13.6473 6.91176M17.4995 12.1841C16.5378 15.2609 13.5967 17.5 10.1178 17.5C6.86118 17.5 4.07589 15.5379 2.94432 12.7632L2.41165 11.3235M2.41165 11.3235L1.5293 15.7353M2.41165 11.3235L6.38224 13.0882"></path></g></svg></button><button tabindex="0" type="button" class="pencraft pc-reset pencraft icon-container view-image"><svg xmlns="http://www.w3.org/2000/svg" width="20" height="20" viewBox="0 0 24 24" fill="none" stroke="currentColor" stroke-width="2" stroke-linecap="round" stroke-linejoin="round" class="lucide lucide-maximize2 lucide-maximize-2"><polyline points="15 3 21 3 21 9"></polyline><polyline points="9 21 3 21 3 15"></polyline><line x1="21" x2="14" y1="3" y2="10"></line><line x1="3" x2="10" y1="21" y2="14"></line></svg></button></div></div></div></a></figure></div><p>Plaats alle verdere code in <code>main.py</code> in het <code>if __name__ == "__main__"</code>-blok. Je kunt het downloaden en opschonen van de gegevens &#233;&#233;n keer uitvoeren, en dan (tijdelijk) uitschakelen met comments.</p><h2>Kolommen selecteren</h2><p>De eenvoudigste manier om een bepaalde kolom te selecteren, is met <code>[]</code>. Om bijvoorbeeld de kolom <code>Jaargemiddelde</code> te selecteren:</p><pre><code><code>jaargemiddelde = df["Jaargemiddelde"]
</code></code></pre><p>Dit is herkenbaar, omdat het lijkt op hoe je bijvoorbeeld een waarde uit een <code>dict</code> ophaalt op basis van de sleutel. Echter, in de documentatie van <code>pandas</code> raden ze dit af. Ook een andere eenvoudige manier raden ze af:</p><pre><code><code>jaargemiddelde = df.Jaargemiddelde
</code></code></pre><p>Voor snelle inspectie is dit prima, maar voor productiecode raden ze andere manieren aan.</p><h3>loc</h3><p>Met <code>.loc</code> gebruik je labels om kolommen en rijen te selecteren. In het geval van kolommen, is de label de kolomnaam. De algemene structuur van <code>.loc</code> is:</p><pre><code><code>df.loc[row_indexer,column_indexer]
</code></code></pre><p>Om &#233;&#233;n of meer kolommen te selecteren waarbij je alle rijen wilt houden, gebruik je <code>:</code> op de plaats van de rij-selectie. Dus om de kolom "Jaargemiddelde" te selecteren:</p><pre><code><code>jaargemiddelde = df.loc[:, "Jaargemiddelde"]
</code></code></pre><pre><code><code>0       8.7
1       8.2
2       9.2
3       8.9
4       8.7
       ... 
119    11.7
120    10.5
121    11.6
122    11.8
123     NaN
</code></code></pre><p>Wil je nu meerdere kolommen selecteren, dan gebruik je een lijst met kolomnamen:</p><pre><code><code>jaargemiddelde = df.loc[:, ["Jaar", "Jaargemiddelde"]]
</code></code></pre><pre><code><code>     Jaar  Jaargemiddelde
0    1901             8.7
1    1902             8.2
2    1903             9.2
3    1904             8.9
4    1905             8.7
..    ...             ...
119  2020            11.7
120  2021            10.5
121  2022            11.6
122  2023            11.8
123  2024             NaN
</code></code></pre><p>Zou je "Jaar" en "Jaargemiddelde" omdraaien in de lijst, dan zijn de kolommen in de resulterende <code>DataFrame</code> ook omgedraaid.</p><p>Je kunt ook <em>slicing</em> toepassen. Stel dat je alle maandkolommen wilt selecteren:</p><pre><code><code>maanden = df.loc[:, "Januari": "December"]
</code></code></pre><p>Het is hiervoor dus wel belangrijk dat je van tevoren je gegevens hebt ge&#239;nspecteerd, zodat je weet welke kolommen je precies selecteert die tussen "Januari" en "December" liggen. Let ook op dat, in tegenstelling tot gebruikelijk in Python, de <em>slice</em> <strong>tot en met</strong> is, dus ook "December" wordt geselecteerd.</p><h3>Filteren</h3><p>Je kunt <code>.loc</code> ook gebruiken met een lijst van <em>booleans</em> om tot een selectie te komen. Maak eerst een lijst met <em>booleans</em>. De volgende code kijkt of de "r" in de kolomnaam voorkomt. Let op dat je niet <code>df</code> gebruikt (de gehele dataset), maar <code>maanden</code>, de hiervoor aangemaakte <code>DataFrame</code> met enkel de maanden kolommen.</p><pre><code><code>r_maanden = maanden.columns.str.contains("r")
</code></code></pre><pre><code><code>[True  True  True  True False False False False  True  True  True  True]
</code></code></pre><p>Nu kun je deze lijst met <em>booleans</em> gebruiken in de selectie:</p><pre><code><code>r_maanden = maanden.loc[:, r_maanden]
</code></code></pre><p>Of, in &#233;&#233;n stap:</p><pre><code><code>r_maanden = maanden.loc[:, maanden.columns.str.contains("r")]
</code></code></pre><p>Let er altijd op dat de lengte van de lijst met <em>booleans</em> even lang is als het aantal kolommen. In dit geval bevat <code>maanden</code> 12 kolommen, en moet de lijst met <em>booleans</em> dus ook 12 lang zijn. Je kunt het zo zien: de lijst met <em>booleans</em> schuif je over de kolomnamen heen. Alle kolommen met <code>True</code> selecteer je (blijven behouden).</p><div class="captioned-image-container"><figure><a class="image-link image2" target="_blank" href="https://substackcdn.com/image/fetch/$s_!vGLE!,f_auto,q_auto:good,fl_progressive:steep/https%3A%2F%2Fsubstack-post-media.s3.amazonaws.com%2Fpublic%2Fimages%2Fc1ed855a-77d2-45fb-992c-84cecb2551a7_1951x130.png" data-component-name="Image2ToDOM"><div class="image2-inset"><picture><source type="image/webp" srcset="https://substackcdn.com/image/fetch/$s_!vGLE!,w_424,c_limit,f_webp,q_auto:good,fl_progressive:steep/https%3A%2F%2Fsubstack-post-media.s3.amazonaws.com%2Fpublic%2Fimages%2Fc1ed855a-77d2-45fb-992c-84cecb2551a7_1951x130.png 424w, https://substackcdn.com/image/fetch/$s_!vGLE!,w_848,c_limit,f_webp,q_auto:good,fl_progressive:steep/https%3A%2F%2Fsubstack-post-media.s3.amazonaws.com%2Fpublic%2Fimages%2Fc1ed855a-77d2-45fb-992c-84cecb2551a7_1951x130.png 848w, https://substackcdn.com/image/fetch/$s_!vGLE!,w_1272,c_limit,f_webp,q_auto:good,fl_progressive:steep/https%3A%2F%2Fsubstack-post-media.s3.amazonaws.com%2Fpublic%2Fimages%2Fc1ed855a-77d2-45fb-992c-84cecb2551a7_1951x130.png 1272w, https://substackcdn.com/image/fetch/$s_!vGLE!,w_1456,c_limit,f_webp,q_auto:good,fl_progressive:steep/https%3A%2F%2Fsubstack-post-media.s3.amazonaws.com%2Fpublic%2Fimages%2Fc1ed855a-77d2-45fb-992c-84cecb2551a7_1951x130.png 1456w" sizes="100vw"><img src="https://substackcdn.com/image/fetch/$s_!vGLE!,w_1456,c_limit,f_auto,q_auto:good,fl_progressive:steep/https%3A%2F%2Fsubstack-post-media.s3.amazonaws.com%2Fpublic%2Fimages%2Fc1ed855a-77d2-45fb-992c-84cecb2551a7_1951x130.png" width="1456" height="97" data-attrs="{&quot;src&quot;:&quot;https://substack-post-media.s3.amazonaws.com/public/images/c1ed855a-77d2-45fb-992c-84cecb2551a7_1951x130.png&quot;,&quot;srcNoWatermark&quot;:null,&quot;fullscreen&quot;:null,&quot;imageSize&quot;:null,&quot;height&quot;:97,&quot;width&quot;:1456,&quot;resizeWidth&quot;:null,&quot;bytes&quot;:null,&quot;alt&quot;:&quot;Gebruik booleans om kolommen te selecteren&quot;,&quot;title&quot;:null,&quot;type&quot;:null,&quot;href&quot;:null,&quot;belowTheFold&quot;:true,&quot;topImage&quot;:false,&quot;internalRedirect&quot;:null,&quot;isProcessing&quot;:false,&quot;align&quot;:null,&quot;offset&quot;:false}" class="sizing-normal" alt="Gebruik booleans om kolommen te selecteren" title="Gebruik booleans om kolommen te selecteren" srcset="https://substackcdn.com/image/fetch/$s_!vGLE!,w_424,c_limit,f_auto,q_auto:good,fl_progressive:steep/https%3A%2F%2Fsubstack-post-media.s3.amazonaws.com%2Fpublic%2Fimages%2Fc1ed855a-77d2-45fb-992c-84cecb2551a7_1951x130.png 424w, https://substackcdn.com/image/fetch/$s_!vGLE!,w_848,c_limit,f_auto,q_auto:good,fl_progressive:steep/https%3A%2F%2Fsubstack-post-media.s3.amazonaws.com%2Fpublic%2Fimages%2Fc1ed855a-77d2-45fb-992c-84cecb2551a7_1951x130.png 848w, https://substackcdn.com/image/fetch/$s_!vGLE!,w_1272,c_limit,f_auto,q_auto:good,fl_progressive:steep/https%3A%2F%2Fsubstack-post-media.s3.amazonaws.com%2Fpublic%2Fimages%2Fc1ed855a-77d2-45fb-992c-84cecb2551a7_1951x130.png 1272w, https://substackcdn.com/image/fetch/$s_!vGLE!,w_1456,c_limit,f_auto,q_auto:good,fl_progressive:steep/https%3A%2F%2Fsubstack-post-media.s3.amazonaws.com%2Fpublic%2Fimages%2Fc1ed855a-77d2-45fb-992c-84cecb2551a7_1951x130.png 1456w" sizes="100vw" loading="lazy"></picture><div></div></div></a></figure></div><h2>Rijen selecteren</h2><p>Tot dusver heb je <code>.loc</code> gebruikt om kolommen te selecteren, waarbij je alle rijen steeds behield. Je kunt <code>.loc</code> ook gebruiken om bepaalde rijen te selecteren. Dit werkt exact hetzelfde als bij de kolommen, maar dan als eerste argument in <code>.loc[]</code>, voor de komma.</p><p>Om bijvoorbeeld de eerste rij te selecteren, gebruik je <code>df.loc[0, :]</code>. In dit geval zijn de labels 0...n, maar let op dat de label <em>niet</em> hetzelfde is als de index bij bijvoorbeeld een Python lijst. In het volgende voorbeeld stel je de index in op de waarden van de kolom "Jaar".</p><pre><code><code>df.set_index("Jaar", inplace=True)
</code></code></pre><pre><code><code>      Station  Januari  Februari  ...  November  December  Jaargemiddelde
Jaar                              ...                                    
1901      260     -0.3      -0.9  ...       5.2       3.0             8.7
1902      260      4.7      -0.3  ...       3.7       0.8             8.2
1903      260      3.0       5.7  ...       5.6       0.9             9.2
1904      260      0.8       3.0  ...       5.5       4.0             8.9
1905      260      1.3       3.6  ...       3.8       2.4             8.7
</code></code></pre><p>Wil je nu de eerste rij selecteren - het eerste jaar dus -, dan gebruik je <code>df.loc[1901, :]</code>. Meerdere rijen selecteren werkt hetzelfde als bij meerdere kolommen selecteren. Je geeft een lijst op met de gewenste rijen, of een <em>slice</em>.</p><pre><code><code>eerste_drie_jaren = df.loc[[1901, 1902, 1903], :]
eerste_drie_jaren = df.loc[1901:1903, :]
</code></code></pre><h3>Filteren</h3><p>Ook nu kun je weer een lijst met <em>booleans</em> gebruiken om de rijen te filteren. Stel dat je alleen de jaren wilt overhouden waar de gemiddelde temperatuur in januari hoger dan 0 graden was:</p><pre><code><code>selector = df.loc[:, "Januari"] &gt; 0
januari_boven_0 = df.loc[selector, :]
</code></code></pre><p>De lijst met <em>booleans</em> moet net zo lang zijn als het aantal rijen van de <code>DataFrame</code>. In de eerste stap selecteer je dus eerst de kolom "Januari" met <code>df.loc[:, "Januari"]</code>. Vervolgens gebruik je <code>&gt; 0</code> om voor elke rij te kijken of de waarde groter is dan nul. Per rij levert dit dus <code>True</code> of <code>False</code> op. Deze lijst met <em>booleans</em> voeg je in de tweede stap toe aan <code>.loc[]</code>, op de plaats van de te selecteren rijen. Ook hier kun je het weer zien alsof je de lijst met <em>booleans</em> over de rij-labels heen schuift en alleen de rijen met <code>True</code> selecteert.</p><h3>Alleen rijen</h3><p>Overigens is het zo dat als je alle kolommen wilt behouden, je de <code>:</code> mag weglaten. Dit levert dus hetzelfde resultaat op:</p><pre><code><code>eerste_drie_jaren = df.loc[1901:1903, :]
eerste_drie_jaren = df.loc[1901:1903]
</code></code></pre><p>Ofwel: gebruik je maar &#233;&#233;n argument in <code>.loc[]</code>, dan maak je automatisch selecties van de rijen.</p><h2>Rijen en kolommen filteren</h2><p>Je hebt nu steeds op alleen de kolommen &#243;f alleen de rijen gewerkt. Maar <code>.loc[]</code> staat uiteraard toe dat je tegelijkertijd kolommen en rijen selecteert. Bijvoorbeeld, alleen de gegevens van het eerste kwartaal voor de jaren 1980-1990:</p><pre><code><code>q1_80 = df.loc[1980:1990, "Januari": "Maart"]
</code></code></pre><p>Of alleen het jaargemiddelde voor de jaren waar het jaargemiddelde boven de 10 graden lag:</p><pre><code><code>boven_10 = df.loc[df.loc[:, "Jaargemiddelde"] &gt; 10, "Jaargemiddelde"]
</code></code></pre><h2>Overige methodes</h2><p>Met <code>.loc</code> kom je een heel eind, maar er zijn ook andere methodes om gegevens te selecteren.</p><h3>iloc</h3><p>Net als <code>.loc</code>, kun je <code>.iloc</code> gebruiken om kolommen en/of rijen te selecteren. De werking is vrijwel hetzelfde, met het grote verschil dat je bij <code>.iloc</code> de integer gebaseerde positie gebruikt, in plaats van de labels. Je kunt <code>.iloc</code> vergelijken met het ophalen van items uit een lijst, waar je ook de integer gebaseerde index gebruikt. Net als bij een lijst, begin je bij <code>.iloc</code> ook met tellen bij 0.</p><p>Dus om de eerste drie jaren op te halen gebruik je <code>.loc</code> met labels, of <code>.iloc</code> met de integer gebaseerde index. (NB: je had de index op het jaartal gezet, weet je nog?)</p><pre><code><code>    eerste_drie_jaren = df.loc[1901:1903]  # Let op: 1903 is inclusief
    eerste_drie_jaren = df.iloc[0:3]  # Let op: 3 is exclusief
</code></code></pre><p>Je ziet dat een groot - en eerlijk gezegd verwarrend - verschil is, dat bij <code>.loc</code> zowel de start- als eindpositie <em>inclusief</em> zijn. Bij <code>.iloc</code> is alleen de startpositie inclusief, de eindpositie is exclusief.</p><h3>at en iat</h3><p>Heb je de waarde uit een enkele cel nodig, dan kun je dat uiteraard met <code>.loc</code> of <code>.iloc</code> doen. Maar voor deze speciale situatie zijn ook <code>.at</code> en <code>iat</code> beschikbaar. Ze werken hetzelfde als <code>.loc</code> en <code>.iloc</code>, met het verschil dat je dus maar &#233;&#233;n rij en &#233;&#233;n kolom kunt opgeven. Dit zorgt ervoor dat het een iets snellere handeling is.</p><pre><code><code>jan_2020 = df.at[2020, "Januari"]
</code></code></pre><h2>Conclusie</h2><p>Als je net met <code>pandas</code> begint, kan het selecteren van de juiste gegevens uit een <code>DataFrame</code> lastig zijn. De regels van <code>.loc</code> en aanverwanten zijn misschien niet helemaal intu&#239;tief, bijvoorbeeld dat je <code>.loc[]</code> gebruikt in plaats van <code>.loc()</code>. Of dat bij een <em>slice</em> de eindwaarde &#243;&#243;k inclusief is (maar bij <code>.iloc</code> dan weer niet).</p><p>Het is even wennen misschien. Daarom is het wel aan te raden om er goed mee te oefenen, want het zal zelden zijn dat je de hele <code>DataFrame</code> nodig hebt.</p><p>Alle code voorbeelden en voorbeeldbestanden van dit project zijn ook op <a href="https://github.com/python-cursus-nl/pandas-gegevens-inlezen-en-opslaan">Github</a> te bekijken.</p>]]></content:encoded></item><item><title><![CDATA[Gegevens inlezen en opslaan met pandas]]></title><description><![CDATA[In pandas werk je vaak met gegevens in een DataFrame, maar je gegevens komen vaak ergens anders vandaan.In deze tutorial leer je hoe je gegevens inleest vanuit CSV, Excel en JSON. Ook leer je hoe je een DataFrame weer opslaat in &#233;&#233;n van deze formaten.]]></description><link>https://pythoncursus.substack.com/p/gegevens-inlezen-en-opslaan-met-pandas</link><guid isPermaLink="false">https://pythoncursus.substack.com/p/gegevens-inlezen-en-opslaan-met-pandas</guid><dc:creator><![CDATA[python-cursus.nl]]></dc:creator><pubDate>Thu, 13 Jun 2024 14:10:25 GMT</pubDate><enclosure url="https://substack-post-media.s3.amazonaws.com/public/images/b7eade74-f321-47e0-8ee4-2bc606647010_1024x1024.webp" length="0" type="image/jpeg"/><content:encoded><![CDATA[<p>In een <a href="https://python-cursus.nl/blog/pandas-introductie/">vorige tutorial</a> heb je kennisgemaakt met <a href="https://pandas.pydata.org/">pandas</a>, een veelgebruikte tool voor het werken met gegegevens in Python.</p><p>In <code>pandas</code> werk je vaak met gegevens in een <code>DataFrame</code> (of een <code>Series</code>), maar je gegevens komen vaak ergens anders vandaan. In deze tutorial leer je hoe je gegevens inleest vanuit CSV, Excel en JSON. Ook leer je hoe je een <code>DataFrame</code> weer opslaat in &#233;&#233;n van deze formaten.</p><h2>Voordat je begint</h2><p>Maak een nieuw project aan, met een nieuwe <a href="https://python-cursus.nl/blog/pip-en-venv/">virtual environment</a>. Installeer daarin ook <code>pandas</code>.</p><pre><code><code>cd projects
mkdir pandas_gegevens &amp;&amp; cd pandas_gegevens
python -m venv .venv
source .venv/bin/activate  # .venv\Scripts\activate.bat voor Windows
python -m pip install pandas
python -m pip freeze &gt; requirements.txt
</code></code></pre><p>Alle code voorbeelden en voorbeeldbestanden van dit project zijn ook op <a href="https://github.com/python-cursus-nl/pandas-gegevens-inlezen-en-opslaan">Github</a> te bekijken.</p><h2>Series en DataFrames</h2><p>Veelal zul je in <code>pandas</code> met <code>Series</code> en <code>DataFrames</code> werken. Maar wat is een <code>DataFrame</code> precies? Je kunt een <code>DataFrame</code> het best vergelijken met een tabel zoals je die ziet als je een spreadsheet opent (bijvoorbeeld in Excel). Je hebt dus rijen en kolommen.</p><div class="captioned-image-container"><figure><a class="image-link image2 is-viewable-img" target="_blank" href="https://substackcdn.com/image/fetch/$s_!TYf5!,f_auto,q_auto:good,fl_progressive:steep/https%3A%2F%2Fsubstack-post-media.s3.amazonaws.com%2Fpublic%2Fimages%2Fb64c4b8e-64a7-4aeb-a700-e516715f543d_541x394.svg" data-component-name="Image2ToDOM"><div class="image2-inset"><picture><source type="image/webp" srcset="https://substackcdn.com/image/fetch/$s_!TYf5!,w_424,c_limit,f_webp,q_auto:good,fl_progressive:steep/https%3A%2F%2Fsubstack-post-media.s3.amazonaws.com%2Fpublic%2Fimages%2Fb64c4b8e-64a7-4aeb-a700-e516715f543d_541x394.svg 424w, https://substackcdn.com/image/fetch/$s_!TYf5!,w_848,c_limit,f_webp,q_auto:good,fl_progressive:steep/https%3A%2F%2Fsubstack-post-media.s3.amazonaws.com%2Fpublic%2Fimages%2Fb64c4b8e-64a7-4aeb-a700-e516715f543d_541x394.svg 848w, https://substackcdn.com/image/fetch/$s_!TYf5!,w_1272,c_limit,f_webp,q_auto:good,fl_progressive:steep/https%3A%2F%2Fsubstack-post-media.s3.amazonaws.com%2Fpublic%2Fimages%2Fb64c4b8e-64a7-4aeb-a700-e516715f543d_541x394.svg 1272w, https://substackcdn.com/image/fetch/$s_!TYf5!,w_1456,c_limit,f_webp,q_auto:good,fl_progressive:steep/https%3A%2F%2Fsubstack-post-media.s3.amazonaws.com%2Fpublic%2Fimages%2Fb64c4b8e-64a7-4aeb-a700-e516715f543d_541x394.svg 1456w" sizes="100vw"><img src="https://substackcdn.com/image/fetch/$s_!TYf5!,w_1456,c_limit,f_auto,q_auto:good,fl_progressive:steep/https%3A%2F%2Fsubstack-post-media.s3.amazonaws.com%2Fpublic%2Fimages%2Fb64c4b8e-64a7-4aeb-a700-e516715f543d_541x394.svg" width="1456" height="1062" data-attrs="{&quot;src&quot;:&quot;https://substack-post-media.s3.amazonaws.com/public/images/b64c4b8e-64a7-4aeb-a700-e516715f543d_541x394.svg&quot;,&quot;srcNoWatermark&quot;:null,&quot;fullscreen&quot;:null,&quot;imageSize&quot;:null,&quot;height&quot;:1062,&quot;width&quot;:1456,&quot;resizeWidth&quot;:null,&quot;bytes&quot;:null,&quot;alt&quot;:&quot;DataFrame&quot;,&quot;title&quot;:null,&quot;type&quot;:null,&quot;href&quot;:null,&quot;belowTheFold&quot;:false,&quot;topImage&quot;:true,&quot;internalRedirect&quot;:null,&quot;isProcessing&quot;:false,&quot;align&quot;:null,&quot;offset&quot;:false}" class="sizing-normal" alt="DataFrame" title="DataFrame" srcset="https://substackcdn.com/image/fetch/$s_!TYf5!,w_424,c_limit,f_auto,q_auto:good,fl_progressive:steep/https%3A%2F%2Fsubstack-post-media.s3.amazonaws.com%2Fpublic%2Fimages%2Fb64c4b8e-64a7-4aeb-a700-e516715f543d_541x394.svg 424w, https://substackcdn.com/image/fetch/$s_!TYf5!,w_848,c_limit,f_auto,q_auto:good,fl_progressive:steep/https%3A%2F%2Fsubstack-post-media.s3.amazonaws.com%2Fpublic%2Fimages%2Fb64c4b8e-64a7-4aeb-a700-e516715f543d_541x394.svg 848w, https://substackcdn.com/image/fetch/$s_!TYf5!,w_1272,c_limit,f_auto,q_auto:good,fl_progressive:steep/https%3A%2F%2Fsubstack-post-media.s3.amazonaws.com%2Fpublic%2Fimages%2Fb64c4b8e-64a7-4aeb-a700-e516715f543d_541x394.svg 1272w, https://substackcdn.com/image/fetch/$s_!TYf5!,w_1456,c_limit,f_auto,q_auto:good,fl_progressive:steep/https%3A%2F%2Fsubstack-post-media.s3.amazonaws.com%2Fpublic%2Fimages%2Fb64c4b8e-64a7-4aeb-a700-e516715f543d_541x394.svg 1456w" sizes="100vw" fetchpriority="high"></picture><div class="image-link-expand"><div class="pencraft pc-display-flex pc-gap-8 pc-reset"><button tabindex="0" type="button" class="pencraft pc-reset pencraft icon-container restack-image"><svg aria-hidden="true" width="20" height="20" viewBox="0 0 20 20" fill="none" stroke-width="1.5" stroke="var(--color-fg-primary)" stroke-linecap="round" stroke-linejoin="round" xmlns="http://www.w3.org/2000/svg"><g><path d="M2.53001 7.81595C3.49179 4.73911 6.43281 2.5 9.91173 2.5C13.1684 2.5 15.9537 4.46214 17.0852 7.23684L17.6179 8.67647M17.6179 8.67647L18.5002 4.26471M17.6179 8.67647L13.6473 6.91176M17.4995 12.1841C16.5378 15.2609 13.5967 17.5 10.1178 17.5C6.86118 17.5 4.07589 15.5379 2.94432 12.7632L2.41165 11.3235M2.41165 11.3235L1.5293 15.7353M2.41165 11.3235L6.38224 13.0882"></path></g></svg></button><button tabindex="0" type="button" class="pencraft pc-reset pencraft icon-container view-image"><svg xmlns="http://www.w3.org/2000/svg" width="20" height="20" viewBox="0 0 24 24" fill="none" stroke="currentColor" stroke-width="2" stroke-linecap="round" stroke-linejoin="round" class="lucide lucide-maximize2 lucide-maximize-2"><polyline points="15 3 21 3 21 9"></polyline><polyline points="9 21 3 21 3 15"></polyline><line x1="21" x2="14" y1="3" y2="10"></line><line x1="3" x2="10" y1="21" y2="14"></line></svg></button></div></div></div></a></figure></div><p>De donkergrijze rij bevat de kolomnamen en de donkergrijze eerste kolom bevat de index (rijnummers of namen).</p><h3>Series</h3><p>Omdat een kolom van het type <code>Series</code> is, is het handig om hiermee te beginnen. Een <code>Series</code> is een 1-dimensionale lijst die elk type data kan bevatten, bijvoorbeeld <code>int</code>, <code>str</code> of <code>date</code>. Voor beginners is het soms verwarrend, maar een <code>Series</code> heeft ook een <em>index</em>, ofwel labels.</p><div class="captioned-image-container"><figure><a class="image-link image2 is-viewable-img" target="_blank" href="https://substackcdn.com/image/fetch/$s_!EMUz!,f_auto,q_auto:good,fl_progressive:steep/https%3A%2F%2Fsubstack-post-media.s3.amazonaws.com%2Fpublic%2Fimages%2F0bb6a1c6-f52c-444f-8943-e0e790560ef8_196x307.svg" data-component-name="Image2ToDOM"><div class="image2-inset"><picture><source type="image/webp" srcset="https://substackcdn.com/image/fetch/$s_!EMUz!,w_424,c_limit,f_webp,q_auto:good,fl_progressive:steep/https%3A%2F%2Fsubstack-post-media.s3.amazonaws.com%2Fpublic%2Fimages%2F0bb6a1c6-f52c-444f-8943-e0e790560ef8_196x307.svg 424w, https://substackcdn.com/image/fetch/$s_!EMUz!,w_848,c_limit,f_webp,q_auto:good,fl_progressive:steep/https%3A%2F%2Fsubstack-post-media.s3.amazonaws.com%2Fpublic%2Fimages%2F0bb6a1c6-f52c-444f-8943-e0e790560ef8_196x307.svg 848w, https://substackcdn.com/image/fetch/$s_!EMUz!,w_1272,c_limit,f_webp,q_auto:good,fl_progressive:steep/https%3A%2F%2Fsubstack-post-media.s3.amazonaws.com%2Fpublic%2Fimages%2F0bb6a1c6-f52c-444f-8943-e0e790560ef8_196x307.svg 1272w, https://substackcdn.com/image/fetch/$s_!EMUz!,w_1456,c_limit,f_webp,q_auto:good,fl_progressive:steep/https%3A%2F%2Fsubstack-post-media.s3.amazonaws.com%2Fpublic%2Fimages%2F0bb6a1c6-f52c-444f-8943-e0e790560ef8_196x307.svg 1456w" sizes="100vw"><img src="https://substackcdn.com/image/fetch/$s_!EMUz!,w_1456,c_limit,f_auto,q_auto:good,fl_progressive:steep/https%3A%2F%2Fsubstack-post-media.s3.amazonaws.com%2Fpublic%2Fimages%2F0bb6a1c6-f52c-444f-8943-e0e790560ef8_196x307.svg" width="1456" height="2288" data-attrs="{&quot;src&quot;:&quot;https://substack-post-media.s3.amazonaws.com/public/images/0bb6a1c6-f52c-444f-8943-e0e790560ef8_196x307.svg&quot;,&quot;srcNoWatermark&quot;:null,&quot;fullscreen&quot;:null,&quot;imageSize&quot;:null,&quot;height&quot;:2288,&quot;width&quot;:1456,&quot;resizeWidth&quot;:null,&quot;bytes&quot;:null,&quot;alt&quot;:&quot;Series&quot;,&quot;title&quot;:null,&quot;type&quot;:null,&quot;href&quot;:null,&quot;belowTheFold&quot;:true,&quot;topImage&quot;:false,&quot;internalRedirect&quot;:null,&quot;isProcessing&quot;:false,&quot;align&quot;:null,&quot;offset&quot;:false}" class="sizing-normal" alt="Series" title="Series" srcset="https://substackcdn.com/image/fetch/$s_!EMUz!,w_424,c_limit,f_auto,q_auto:good,fl_progressive:steep/https%3A%2F%2Fsubstack-post-media.s3.amazonaws.com%2Fpublic%2Fimages%2F0bb6a1c6-f52c-444f-8943-e0e790560ef8_196x307.svg 424w, https://substackcdn.com/image/fetch/$s_!EMUz!,w_848,c_limit,f_auto,q_auto:good,fl_progressive:steep/https%3A%2F%2Fsubstack-post-media.s3.amazonaws.com%2Fpublic%2Fimages%2F0bb6a1c6-f52c-444f-8943-e0e790560ef8_196x307.svg 848w, https://substackcdn.com/image/fetch/$s_!EMUz!,w_1272,c_limit,f_auto,q_auto:good,fl_progressive:steep/https%3A%2F%2Fsubstack-post-media.s3.amazonaws.com%2Fpublic%2Fimages%2F0bb6a1c6-f52c-444f-8943-e0e790560ef8_196x307.svg 1272w, https://substackcdn.com/image/fetch/$s_!EMUz!,w_1456,c_limit,f_auto,q_auto:good,fl_progressive:steep/https%3A%2F%2Fsubstack-post-media.s3.amazonaws.com%2Fpublic%2Fimages%2F0bb6a1c6-f52c-444f-8943-e0e790560ef8_196x307.svg 1456w" sizes="100vw" loading="lazy"></picture><div class="image-link-expand"><div class="pencraft pc-display-flex pc-gap-8 pc-reset"><button tabindex="0" type="button" class="pencraft pc-reset pencraft icon-container restack-image"><svg aria-hidden="true" width="20" height="20" viewBox="0 0 20 20" fill="none" stroke-width="1.5" stroke="var(--color-fg-primary)" stroke-linecap="round" stroke-linejoin="round" xmlns="http://www.w3.org/2000/svg"><g><path d="M2.53001 7.81595C3.49179 4.73911 6.43281 2.5 9.91173 2.5C13.1684 2.5 15.9537 4.46214 17.0852 7.23684L17.6179 8.67647M17.6179 8.67647L18.5002 4.26471M17.6179 8.67647L13.6473 6.91176M17.4995 12.1841C16.5378 15.2609 13.5967 17.5 10.1178 17.5C6.86118 17.5 4.07589 15.5379 2.94432 12.7632L2.41165 11.3235M2.41165 11.3235L1.5293 15.7353M2.41165 11.3235L6.38224 13.0882"></path></g></svg></button><button tabindex="0" type="button" class="pencraft pc-reset pencraft icon-container view-image"><svg xmlns="http://www.w3.org/2000/svg" width="20" height="20" viewBox="0 0 24 24" fill="none" stroke="currentColor" stroke-width="2" stroke-linecap="round" stroke-linejoin="round" class="lucide lucide-maximize2 lucide-maximize-2"><polyline points="15 3 21 3 21 9"></polyline><polyline points="9 21 3 21 3 15"></polyline><line x1="21" x2="14" y1="3" y2="10"></line><line x1="3" x2="10" y1="21" y2="14"></line></svg></button></div></div></div></a></figure></div><p>De donkergrijze blokken zijn samen de index, terwijl de lichtgrijze blokken de gegevens bevatten. Dus een <code>Series</code> is 1-dimensionaal (het is &#233;&#233;n lijst met gegevens), maar elk gegeven heeft wel een index. Maak je een <code>Series</code> aan zonder de index te specificeren, dan loopt de index simpelweg van 0 tot <em>n</em>.</p><p>Om een <code>Series</code> te maken, plaats je in <code>main.py</code> bovenin de import <code>import pandas as pd</code>. Deze manier van <code>pandas</code> importeren is een gewoonte die is ontstaan en die je overal zult zien. Het is dus handig jezelf dit ook aan te leren.</p><p>Vervolgens kun je een eenvoudige <code>Series</code> maken zonder index op te geven. Plaats hiervoor een lijst met gegevens in <code>pd.Series()</code>.</p><pre><code><code>import pandas as pd

# Maak een Series aan zonder index
s = pd.Series(["aap", "noot", "mies"])
print(s)
</code></code></pre><pre><code><code>0     aap
1    noot
2    mies
dtype: object
</code></code></pre><p>Je ziet dat de index nu loopt van 0 t/m 2. Verder zie je <code>dtype: object</code>. Dit is kort voor <em>datatype</em>. In dit geval is het type "object", wat elk Python-object kan omvatten, inclusief <code>str</code>. Standaard slaat <code>pandas</code> tekst op als "object". Dit kan voor fouten zorgen, want een object kan ook iets anders zijn, en je kunt dus per ongeluk verschillende objecten in je <code>Series</code> plaatsen.</p><p>Om dit te voorkomen kun je opgeven dat je <code>string</code> of <code>pd.StringDtype()</code> als <code>dtype</code> wilt:</p><pre><code><code># Maak een Series aan zonder index, met type `str`
s = pd.Series(["aap", "noot", "mies"], dtype="string")
# s = pd.Series(["aap", "noot", "mies"], dtype=pd.StringDtype())
print(s)
</code></code></pre><pre><code><code>0     aap
1    noot
2    mies
dtype: string
</code></code></pre><p>Je ziet dat <code>dtype</code> nu wel "string" is.</p><p>Je kunt ook een eigen index meegeven:</p><pre><code><code># Maak een Series aan met index
s = pd.Series(["aap", "noot", "mies"], index=["a", "b", "c"], dtype="string")
print(s)
</code></code></pre><pre><code><code>a     aap
b    noot
c    mies
dtype: string
</code></code></pre><p>En nu kun je de waarde op index "a" ophalen:</p><pre><code><code># Haal waarde op index 'a' op
aap = s["a"]
print(aap)  # aap
</code></code></pre><p>Tot slot kun je ook een <code>Series</code> aanmaken op basis van een <code>dict</code>, waarbij de <em>keys</em> de index zullen vormen. Vergeet dan niet de <code>dtype</code> achteraf nog in te stellen op "string".</p><pre><code><code># Maak een Serie op basis van een `dict`
d = {
    "a": "aap",
    "b": "noot",
    "c": "mies"
}

s = pd.Series(d).astype("string")  # Stel de dtype in op "string"
print(s)
</code></code></pre><pre><code><code>a     aap
b    noot
c    mies
dtype: string
</code></code></pre><h3>DataFrame</h3><p>Een <code>DataFrame</code> is feitelijk een combinatie van meerdere <code>Series</code>, waarbij de index van de <code>Series</code> vervalt en de <code>DataFrame</code> als geheel een index krijgt.</p><pre><code><code># Maak een DataFrame op basis van een `dict` van `Series`.
data = {
    "kolom1": s,  # s is de eerder aangemaakte Serie
    "kolom2": s,  # s is de eerder aangemaakte Serie
}
df = pd.DataFrame(data)
print(df.head())
</code></code></pre><pre><code><code>    kolom1 kolom2
a    aap    aap
b   noot   noot
c   mies   mies
</code></code></pre><p>Met <code>df.head()</code> toon je de eerste 5 rijen van een <code>DataFrame</code>. Je ziet dat de <em>keys</em> van de <code>dict</code> de kolomnamen zijn geworden. De index is ingesteld op "a", "b" en "c", omdat beide <code>Series</code> dit ook hadden. Kan <code>pandas</code> geen goede index bepalen, dan zal het simpelweg 0, 1, 2 worden.</p><p>Je kunt een <code>DataFrame</code> ook maken op basis van een <code>dict</code> met lijsten. Stel ook de index in.</p><pre><code><code># Maak een DataFrame op basis van een `dict` van lijsten
data = {
    "kolom1": [1, 2, 3],
    "kolom2": [4, 5, 6]
}
df = pd.DataFrame(data, index=["rij1", "rij2", "rij3"])
print(df.head())
</code></code></pre><pre><code><code>        kolom1  kolom2
rij1       1       4
rij2       2       5
rij3       3       6
</code></code></pre><p>Je kunt nu een enkele kolom ophalen op basis van de kolomnaam:</p><pre><code><code># Haal een kolom op op basis van label
kolom1 = df["kolom1"]
print(kolom1)
</code></code></pre><pre><code><code>rij1    1
rij2    2
rij3    3
Name: kolom1, dtype: int64
</code></code></pre><p>Met <code>.loc[]</code> haal je een rij op, op basis van de index.</p><pre><code><code># Haal een rij op op basis van index
rij_2 = df.loc["rij2"]
print(rij_2)
print(type(rij_2))
</code></code></pre><pre><code><code>kolom1    2
kolom2    5
Name: rij2, dtype: int64
&lt;class 'pandas.core.series.Series'&gt;
</code></code></pre><p>Je ziet dat ook een rij een <code>Series</code> is, en dat de kolomnamen de index van de <code>Series</code> zijn geworden.</p><h3>Verder lezen</h3><p>Je hebt nu een basisbegrip van <code>Series</code> en <code>DataFrames</code>. Er valt nog veel meer over te vertellen, bijvoorbeeld hoe je meerdere kolommen en rijen kunt selecteren en hoe je gegevens verwijdert, toevoegt en wijzigt. Wil je meer lezen, bekijk dan de <a href="https://pandas.pydata.org/docs/user_guide/dsintro.html">introductie op data structuren in de documentatie van pandas</a>. Ook <a href="https://pandas.pydata.org/docs/user_guide/indexing.html">de documentatie over indexing</a> is interessant.</p><p>Omdat de documentatie van <code>pandas</code> soms overweldigend kan zijn, zullen we in de toekomst ook aparte tutorials wijden aan het werken met <code>Series</code> en <code>DataFrames</code>, en dan met name het selecteren van gegevens.</p><h2>CSV</h2><p>Een CSV-bestand (Comma Separated Values) is een veelgebruikt bestandsformaat voor het uitwisselen van gegevens tussen verschillende applicaties. Het is een eenvoudig tekstbestand waarin elke regel een gegevensrecord voorstelt en de waarden in elk record door komma's (of puntkomma's) van elkaar gescheiden zijn. CSV-bestanden zijn populair omdat ze eenvoudig te openen en bewerken zijn in een teksteditor en omdat je er eenvoudig mee kunt werken in allerlei programma's en programmeertalen.</p><h3>Opslaan als CSV</h3><p>Maak nogmaals een eenvoudige <code>Dataframe</code> aan:</p><pre><code><code># Maak een DataFrame op basis van een `dict` van lijsten
data = {
    "kolom1": [1, 2, 3],
    "kolom2": [4, 5, 6]
}
df = pd.DataFrame(data, index=["rij1", "rij2", "rij3"])
print(df.head())
</code></code></pre><p>Deze <code>Dataframe</code> sla je nu eenvoudig op naar een CSV-bestand. Je gebruikt hiervoor de methode <code>to_csv</code>. Dit is een <em>instance</em>-methode, dus je roept het aan vanaf je net aangemaakte <code>Dataframe</code>: <code>df.to_csv()</code>.</p><p>Er zijn veel opties beschikbaar voor <code>to_csv</code>. In de meeste gevallen heb je er maar enkele nodig. De belangrijkste is natuurlijk het eerste argument: het pad naar een CSV-bestand. Verder zijn <code>sep</code> en <code>index</code> nog interessant. Met <code>sep</code> geef je aan wat het scheidingsteken moet zijn. Standaard is dit een komma (<code>,</code>), maar zelf geef ik altijd de voorkeur aan <code>;</code>. Ook kies ik er meestal voor de <code>index</code> niet weg te schrijven in het CSV-bestand, zodat je een schoon bestand overhoudt met enkel de kolomnamen en de gegevens. Het geheel ziet er dan zo uit:</p><pre><code><code># Schrijf de DataFrame naar een CSV-bestand
df.to_csv("df1.csv", sep=";", index=False)
</code></code></pre><p>En zo eenvoudig is het: je hebt een CSV-bestand genaamd <code>df1.csv</code> opgeslagen! Voor alle opties van <code>to_csv</code> lees je <a href="https://pandas.pydata.org/docs/user_guide/io.html#io-store-in-csv">de documentatie</a>. Overigens kun je <code>to_csv</code> ook op een <code>Series</code> aanroepen.</p><h3>CSV inlezen</h3><p>Andersom - dus het inlezen van een CSV-bestand naar een <code>DataFrame</code> - werkt net zo eenvoudig. Je gebruikt hiervoor <code>read_csv</code>. Deze roep je aan vanaf <code>pd</code> (<code>pandas</code>). Ook hier geef je weer een bestandsnaam op en eventueel het scheidingsteken (als dat geen komma is).</p><pre><code><code># Lees een CSV-bestand in naar een DataFrame
df1 = pd.read_csv("df1.csv", sep=";")
print(df1.head())
</code></code></pre><p>Ook hier zijn weer veel <a href="https://pandas.pydata.org/docs/user_guide/io.html#parsing-options">extra opties</a> mogelijk die helpen bij het goed inlezen van de data. Een paar verdienen extra aandacht.</p><p><strong>Datum</strong></p><p>Met <code>parse_dates</code> en <code>date_format</code> kun je aangeven welke kolommen een datum bevatten, en in welk formaat. Stel dat je een CSV-bestand genaamd <code>datums.csv</code> hebt met de volgende inhoud:</p><pre><code><code>Datum;Aantal
2024-01-01;4
2024-01-02;6
2024-01-03;3
2024-01-04;8
2024-01-05;10
</code></code></pre><p>Met <code>parse_dates</code> en <code>date_format</code> kun je nu aangeven hoe <code>pandas</code> de kolom <code>Datum</code> moet inlezen.</p><pre><code><code># Lees een CSV-bestand in met een datum kolom
df_datums = pd.read_csv("datums.csv", sep=";", parse_dates=["Datum"], date_format="%Y-%m-%d")
print(df_datums.dtypes)
</code></code></pre><pre><code><code>Datum     datetime64[ns]
Aantal             int64
dtype: object
</code></code></pre><p>Je ziet dat <code>Datum</code> nu een <code>dtype</code> van <code>datetime65[ns]</code> heeft, wat wil zeggen dat het niet meer een tekst is, maar echt een datum-formaat. Dit maakt het mogelijk om er allerlei datumafhankelijke bewerkingen mee te doen, zoals het groeperen op maand, of eenvoudig selecteren van alle vrijdagen om maar iets te noemen.</p><p>Zie de documentatie van <a href="https://docs.python.org/3/library/datetime.html#strftime-and-strptime-format-codes">strftime</a> om te leren hoe de datumnotatie werkt.</p><p><strong>Missende waarden</strong></p><p>Een ander handigheidje is dat je kunt bepalen hoe er met missende waarden omgegaan moet worden. Standaard worden al heel wat teksten herkend als missende waarden, namelijk:</p><pre><code><code>['-1.#IND', '1.#QNAN', '1.#IND', '-1.#QNAN', '#N/A N/A', '#N/A', 'N/A', 'n/a', 'NA', '&lt;NA&gt;', '#NA', 'NULL', 'null', 'NaN', '-NaN', 'nan', '-nan', 'None', '']
</code></code></pre><p>Maar heb je zelf ook waarden die als missend herkend moeten woorden, bijvoorbeeld <code>?</code>, dan kan dat met <code>na_values</code>. Neem bijvoorbeeld het CSV-bestand <code>missend.csv</code> met de volgende inhoud:</p><pre><code><code>kolom1;kolom2
1;4
2;?
3;6
</code></code></pre><p>Regel 2, kolom 2 bevat een vraagteken wat <code>pandas</code> moet herkennen als missende waarde.</p><pre><code><code># Lees een CSV-bestand met missende waarden in
df_missend = pd.read_csv("missend.csv", sep=";", na_values="?")
print(df_missend.head())
</code></code></pre><pre><code><code>      kolom1  kolom2
0       1     4.0
1       2     NaN
2       3     6.0
</code></code></pre><p>Je ziet dat op regel 2, kolom 2 nu geen vraagteken meer bevat, maar <code>NaN</code>, waarmee <code>pandas</code> aangeeft dat het een missende waarde is (<em>Not a Number</em>).</p><p>Er zijn nog veel meer mogelijkheden met <code>read_csv</code>, veel heb je alleen in uitzonderlijke gevallen nodig. Bekijk <a href="https://pandas.pydata.org/docs/reference/api/pandas.read_csv.html#pandas.read_csv">de volledige documentatie</a> om meer te leren.</p><h2>Excel</h2><p>Je hebt leren werken met CSV-bestanden, een formaat dat je veel tegenkomt om gegevens uit te wisselen. Een ander veel voorkomend formaat is Excel. Ook hier kan <code>pandas</code> goed mee omgaan, en eigenlijk werkt het vrijwel hetzelfde als werken met CSV. In plaats van <code>to_csv</code> heb je <code>to_excel</code> en in plaats van <code>read_csv</code> heb je <code>read_excel</code>. De opties zijn grotendeels hetzelfde.</p><p>Er is echter &#233;&#233;n belangrijk verschil: voor het werken met Excel heb je nog een andere <em>package</em> nodig die het daadwerkelijk lezen/schrijven doet. Er zijn enkele pakketen beschikbaar:</p><p>Pakket Formaat <a href="https://openpyxl.readthedocs.io/en/stable/">openpyxl</a> .xlsx <a href="https://xlrd.readthedocs.io/en/latest/">xlrd</a> .xls <a href="https://pypi.org/project/pyxlsb/">pyxlsb</a> .xlsb <a href="https://github.com/dimastbk/python-calamine">calamine</a> alle</p><p>Installeer &#233;&#233;n van deze pakketten in je <code>venv</code>, zodat <code>pandas</code> ermee kan werken. Je hoeft verder niets te importeren, dat handelt <code>pandas</code> voor je af.</p><p>Een belangrijk verschil tussen CSV en Excel is dat je in een Excel-bestand meerdere werkbladen (<em>sheets</em>) kunt hebben. Met de parameter <code>sheet_name</code> kun je aangeven welke sheet(s) je wilt inlezen. Standaard staat dit op <code>0</code>, het eerste werkblad. Je kunt de index gebruiken, of de naam van een werkblad. Je kunt ook een lijst met meerdere werkbladen opgeven. Het resultaat is dan een <code>dict</code> met de namen als sleutel en de <code>DataFrames</code> als waarde.</p><p>Een kort voorbeeld. Installeer eerst <code>openpyxl</code>:</p><pre><code><code>python -m pip install openpyxl
</code></code></pre><p>Vervolgens kun je het tweede werkblad van een Excel-bestand genaamd <code>excel.xlxs</code> inlezen.</p><pre><code><code># Lees het tweede werkblad van een Excel-bestand in
df_excel = pd.read_excel("excel.xlsx", sheet_name=1)
print(df_excel.head())
</code></code></pre><pre><code><code>    7   10
0   8  11
1   9  12
</code></code></pre><p>Om een <code>DataFrame</code> naar Excel te schrijven, gebruik je <code>to_excel</code>, wat vrijwel hetzelfde werkt als <code>to_csv</code>. Ook hiervoor heb je een extra <em>package</em> nodig. Je kunt hiervoor <a href="https://xlsxwriter.readthedocs.io/">xlsxwriter</a> of <a href="https://openpyxl.readthedocs.io/en/stable/">openpyxl</a> gebruiken.</p><p>Lees <a href="https://pandas.pydata.org/docs/user_guide/io.html#excel-files">de documentatie</a> om meer te leren over het werken met Excel.</p><h2>JSON</h2><p>Ook het werken met JSON is redelijk vergelijkbaar als met CSV-bestanden en Excel-bestanden. Maar daar waar CSV en Excel erg lijken op een <code>DataFrame</code> (rijen en kolommen), is dit bij JSON niet het geval.</p><p>Een extra optie bij <code>to_json</code> en <code>read_json</code> is dan ook <code>orient</code>, waarmee je bepaalt hoe de vertaalslag van rijen/kolommen naar of van een meer <code>dict</code>-achtige structuur gemaakt moet worden. De standaard bij <code>DataFrames</code> is <code>columns</code>.</p><p>Stel je hebt het volgende JSON-object, opgeslagen in <code>json-bestand.json</code>:</p><pre><code><code>{
  "kolom1": {
    "rij1": 1,
    "rij2": 2,
    "rij3": 3
  },
  "kolom2": {
    "rij1": 4,
    "rij2": 5,
    "rij3": 6
  }
}
</code></code></pre><p>De volgende code leest het bestand in en zet het om naar een <code>DataFrame</code>:</p><pre><code><code># Lees een JSON bestand in
df_json = pd.read_json("json-bestand.json")
print(df_json.head())
</code></code></pre><p>Het resultaat is dan dat de eerste sleutels ("kolom1", "kolom2") de kolom-koppen worden. De tweede-niveau sleutels ("rij1", "rij2", "rij3") vormen de index.</p><pre><code><code>        kolom1  kolom2
rij1       1       4
rij2       2       5
rij3       3       6
</code></code></pre><p>Zie <a href="https://pandas.pydata.org/docs/user_guide/io.html#json">de documentatie</a> om meer te leren over de verschillende ori&#235;ntatiemogelijkheden.</p><h2>Andere bestanden</h2><p>Naast het werken met CSV, Excel en JSON zijn er nog tal van andere mogelijkheden om gegevens naar op te slaan of vanuit te openen. Een aantal voorbeelden zijn HTML, XML, Parquet en SQL. Zie <a href="https://pandas.pydata.org/docs/user_guide/io.html">de documentatie</a> voor een volledige lijst en alle mogelijkheden.</p><h2>Conclusie</h2><p>Gegevens kunnen overal vandaan komen en veel verschillende formaten hebben. In <code>pandas</code> zul je echter meestal met een <code>DataFrame</code> of een <code>Series</code> werken. Gelukkig heeft <code>pandas</code> vele methoden om eenvoudig gegevens in te lezen of juist weg te schrijven naar bijvoorbeeld CSV, Excel en JSON.</p><p>Alle methodes werken ongeveer hetzelfde. Gebruik <code>to_*</code> om naar een bepaald formaat te schrijven en gebruik <code>read_*</code> om vanuit een bepaald formaat in te lezen. Voor de details kun je altijd de documentatie erbij pakken!</p><ul><li><p><a href="https://pandas.pydata.org/docs/user_guide/io.html#">User guide</a></p></li><li><p><a href="https://pandas.pydata.org/docs/reference/io.html">API referentie</a></p></li></ul><p>Succes!</p><p>Alle code voorbeelden en voorbeeldbestanden van dit project zijn ook op <a href="https://github.com/python-cursus-nl/pandas-gegevens-inlezen-en-opslaan">Github</a> te bekijken.</p>]]></content:encoded></item><item><title><![CDATA[Bouw je eigen AI-tools met de OpenAI API]]></title><description><![CDATA[Het is al een poosje geleden, maar er is weer een nieuwe tutorial! Deze keer over hoe je met #python de API van #openai kunt gebruiken. Leer hoe je zelf #AI in je eigen projecten kunt integreren &#129302;]]></description><link>https://pythoncursus.substack.com/p/bouw-je-eigen-ai-tools-met-de-openai</link><guid isPermaLink="false">https://pythoncursus.substack.com/p/bouw-je-eigen-ai-tools-met-de-openai</guid><dc:creator><![CDATA[python-cursus.nl]]></dc:creator><pubDate>Fri, 31 May 2024 10:20:10 GMT</pubDate><enclosure url="https://substack-post-media.s3.amazonaws.com/public/images/9d21eefa-db32-44e5-81e2-274c33fc33c6_1024x1024.webp" length="0" type="image/jpeg"/><content:encoded><![CDATA[<p>Je hebt vast al eens gewerkt met <a href="https://chatgpt.com/">ChatGPT</a> van OpenAI of &#233;&#233;n van de andere varianten, <a href="https://gemini.google.com/app">Gemini</a> van Google of <a href="https://claude.ai">Claude</a> van Anthropic bijvoorbeeld. Zulke diensten zijn leuk en kunnen al veel voor je doen, maar interessanter wordt het als je je eigen <em>tools</em> kunt bouwen met AI.</p><p>In deze tutorial leer je daarom werken met de OpenAI API. Een API is een manier om vanuit je eigen programma te communiceren met een ander programma, vaak via het internet. Je kunt op deze manier de AI-modellen van OpenAI gebruiken zonder naar hun website te gaan.</p><h2>Het project</h2><p>In dit project maak je een script dat jou toetst over een bepaald onderwerp. Je geeft een Wikipedia-URL op en met behulp van AI maak je hier automatisch een samenvatting van &#233;n maak je drie vragen waarmee je kennis over het gegeven onderwerp kunt toetsen. Uiteraard krijg je ook de correcte antwoorden!</p><p>Let op: om te kunnen werken met de API van OpenAI heb je een account nodig. In tegenstelling tot ChatGPT is het gebruik van de API niet gratis, je betaalt naar gebruik. Zie <a href="https://openai.com/api/pricing/">deze pagina</a> voor meer informatie over de kosten. Het kost je niet veel, zeker niet bij kleinere projecten zoals deze. Bovendien kun je een bovengrens instellen, zodat je nooit tegen onverwachte kosten aanloopt.</p><p>Maak nu eerst <a href="https://platform.openai.com/signup">een account</a> als je die nog niet hebt. Nadat je alles hebt ingesteld, kun je een apart project in de OpenAI Dashboard aanmaken. Klik eerst linksbovenin op je (organisatie)naam en vervolgens op "Create project". Maak een nieuw project aan met de naam "WikiTest".</p><p>Vervolgens maak je een API-key aan voor het project. Ga naar de <a href="https://platform.openai.com/api-keys">API keys</a> pagina en klik op "Create new secret key". Geef het een naam, kies het zojuist aangemaakte project en klik op "Create secret key". LET OP: kopieer de API-key direct en sla het op een veilige plek op, je krijgt het niet nogmaals te zien.</p><p>Als je een account en een API-key hebt aangemaakt, maak je een nieuw Python project aan en installeer je de drie externe <em>packages</em> die je gaat gebruiken: <code>openai</code>, <code>wikipedia-api</code> en <code>python-dotenv</code>. De <code>openai</code> <em>package</em> is de <em>client</em> om met de API van OpenAI te communiceren. De <a href="https://wikipedia-api.readthedocs.io/en/latest/README.html">wikipedia-api package</a> is een handig hulpmiddel om pagina's van Wikipedia op basis van een titel op te halen en de tekst in te lezen. Het is een <em>client</em> om te communiceren met de Wikipedia API. De <code>python-dotenv</code> <em>package</em> tot slot is een hulpmiddel om op een veilige manier je API-key in te kunnen lezen, waarover later meer.</p><pre><code><code>cd projects
mkdir wikitest &amp;&amp; cd wikitest
python -m venv .venv
source .venv/bin/activate  # .venv\Scripts\activate.bat voor Windows
python -m pip install openai wikipedia-api python-dotenv
python -m pip freeze &gt; requirements.txt
</code></code></pre><p>Je kunt de code van dit project ook op <a href="https://github.com/python-cursus-nl/wikitest/">Github</a> bekijken.</p><h2>API-key</h2><p>Je API-key is persoonlijk, als deze in de verkeerde handen valt kan iemand het gebruiken en draai jij voor de kosten op. Het is daarom belangrijk om deze geheim te houden. Hier zijn verschillende manieren voor. In dit project gebruik je <code>python-dotenv</code>. Je maakt een <code>.env</code> bestand in je project aan, waarin je de API-key noteert.</p><pre><code><code>OPENAI_API_KEY=sk-proj-123abc</code></code></pre><p>Noem je API-key <code>OPENAI_API_KEY</code> en de openai client zal het automatisch inlezen. Zie ook de <a href="https://platform.openai.com/docs/quickstart/step-2-set-up-your-api-key">documentatie</a> van OpenAI.</p><p>Maak ook een <code>.gitignore</code>-bestand aan, waarin je aangeeft dat het <code>.env</code> bestand niet in je <em>repository</em> opgenomen mag worden.</p><pre><code><code>.env
.venv
*.pyc
__pycache__</code></code></pre><p>Ook al gebruik je nu (nog) geen Git, het is toch verstandig alvast wel een <code>.gitignore</code> aan te maken. Ben je wel zover om versiebeheer te gaan doen met Git en je <em>pusht</em> je code naar bijvoorbeeld Github, dan zal je API-key niet per ongeluk meegestuurd worden.</p><h2>Wikipedia pagina inlezen</h2><p>Om een samenvatting van en toetsvragen over een Wikipedia-pagina te maken, moet je de pagina wel kunnen laten lezen door het AI-model. Er zijn hiervoor verschillende manieren, maar om het zo platform-onafhankelijk te houden, lees je eerst de pagina zelf in als platte tekst. Deze tekst kun je dan weer naar het model sturen.</p><p>Wikipedia heeft gelukkig een API zodat de pagina eenvoudig programmatisch is in te lezen. En met de <em>package</em> <code>wikipedia-api</code> wordt dit proces nog eenvoudiger.</p><p>Maak in je project een bestand <code>wiki.py</code> aan en plaats hierin de volgende code.</p><pre><code><code>import wikipediaapi

USER_AGENT = "WikiTest (info@python-cursus.nl)"
LANGUAGE = "nl"


def read_wiki(titel):
    """Lees een Wikipedia-artikel in."""

    # Initialiseer de Wikipedia client
    wiki = wikipediaapi.Wikipedia(
        user_agent=USER_AGENT,
        language=LANGUAGE,
        extract_format=wikipediaapi.ExtractFormat.WIKI
    )

    # Haal de pagina op, op basis van titel
    page = wiki.page(titel)

    # Controleer of de pagina bestaat
    if not page.exists():
        raise ValueError("Pagina bestaat niet")

    # Geef de tekst terug
    return page.text</code></code></pre><p>Met deze functie kun je een Wikipedia-pagina inlezen op basis van een op te geven titel. Zorg ervoor dat je de <code>USER_AGENT</code> aanpast naar je eigen gegevens.</p><p>Zie <a href="https://wikipedia-api.readthedocs.io/en/latest/README.html">de documentatie</a> van Wikipedia API voor meer over het gebruik van de <em>package</em>.</p><h2>Samenvatting en toets maken</h2><p>Nu je een Wikipedia-pagina in kunt lezen, is de volgende stap het maken van een samenvatting en toetsvragen over de tekst. Hiervoor gebruik je een AI-model, in dit voorbeeld via de API van OpenAI. In de kern is het werken met de API van OpenAI eenvoudig. De aanroep ziet er als volgt uit:</p><pre><code><code>client = OpenAI()
response = client.chat.completions.create(
    model=MODEL,  # Bijvoorbeeld 'gpt-4o'
    response_format={"type": "json_object"},
    messages=[system_message, user_message]
)</code></code></pre><p>Je roept de <em>completions</em>-endpoint aan, met het gekozen model, de bericht(en) die je wilt sturen en optioneel een formaat waarin het antwoord teruggegeven moet worden.</p><p>Er zijn nog meer opties beschikbaar, maar voor nu volstaat dit. Zie de <a href="https://platform.openai.com/docs/api-reference/chat/create">documentatie</a> voor meer informatie.</p><p>De berichten (<em>messages</em>) bestaan uit een lijst met &#233;&#233;n of meer berichten. Een bericht bestaat uit een <code>dict</code> met de sleutels <code>role</code> en <code>content</code>. De rollen zijn onder ander <code>system</code>, <code>user</code> en <code>assistant</code>. De berichten zijn de berichten die je ook in ChatGPT zou zien, dus jouw berichten en de reactie van het model (<code>assistant</code>). Het verschil hier is dat je ook de reactie van het model op kunt sturen. Dit kan nodig zijn als je een applicatie maakt waarbij je de geschiedenis van het gesprek wilt behouden.</p><p>De <code>messages</code> voor dit project kun je als volgt opstellen:</p><pre><code><code>    # Bereid de instructies voor
    # Zorg ervoor dat het model JSON teruggeeft
    system_message = {
        "role": "system",
        "content": """
            Je bent een assistent die Wikipediapagina's samenvat en vragen bedenkt voor
            studenten om hun kennis te toetsen. Je reageert altijd in JSON.
            """
    }

    # Zorg ook voor een voorbeeld van hoe de JSON eruit moet zien.
    # Er zijn geen garanties dat het model zich hier aan houdt, maar
    # in de praktijk gaat het 99% van de tijd wel goed.
    user_prompt = """
        Lees de tekst die hierna volgt\n.
        Maak een samenvatting van de tekst in maximaal drie zinnen\n.
        Maak drie meerkeuzevragen met opties A, B en C over het onderwerp.
        Er kan steeds 1 antwoord goed zijn\n.
        Geef van elke vraag aan wat het goede antwoord is (A, B of C)\n.
        Gebruik het volgende JSON-schema: \n\n

        {
            "samenvatting": "De samenvatting",
            "vragen: [
                {
                    "vraag": "De eerste vraag",
                    "opties: ["A: Optie A", "B: Optie B", "C: Optie C"],
                    "antwoord": A
                },
                {
                    "vraag": "De tweede vraag",
                    "opties: ["A: Optie A", "B: Optie B", "C: Optie C"],
                    "antwoord": B
                },
            ]
        }

        De tekst is als volgt:\n\n
        """

    user_message = {
        "role": "user",
        "content": user_prompt + text
    }
</code></code></pre><p>Hierbij is <code>text</code> op het de een-na-laatste regel de ingelezen Wikipediapagina.</p><p>Het is erg belangrijk om in je <em>prompt</em> aan te geven dat je JSON als output wilt, ook al heb je dit eerder al aangegeven bij het aanroepen van de API. In dit geval is dat in de <em>system prompt</em> gedaan. Zie de <a href="https://platform.openai.com/docs/guides/text-generation/json-mode">JSON mode documentatie</a> voor meer toelichting hierop.</p><p>Het is ook belangrijk om een voorbeeld te geven van de gewenste JSON-structuur. Zo weet het model beter wat het terug moet geven. Doe je dit niet, dan is de kans groter dat bijvoorbeeld de sleutels ("vraag", "opties", "antwoord", etc.) soms een andere naam krijgen (bijvoorbeeld "antwoordopties" in plaats van "opties"). Omdat je later met de sleutels wilt werken, wil je wel dat dit consistent is.</p><p>Werken met Large Language Models blijft fundamenteel onzeker. Door het opgeven van voorbeelden haal je veel van de onzekerheid weg, maar het is niet gegarandeerd dat de gewenste JSON-structuur elke keer wordt teruggegeven. Houd hier rekening mee in de rest van je code.</p><p>Plaats bovenstaande nu in een functie <code>create_text</code>, in een bestand <code>toets.py</code>.</p><pre><code><code>def create_test(text):
    """
    Gebruik OpenAI om de opgegeven tekst samen te vatten en er een toets over te maken.
    """
    client = OpenAI()

    # Bereid de instructies voor
    # Zorg ervoor dat het model JSON teruggeeft
    # ...

    # Haal de response op
    # ...

    return response.choices[0].message.content</code></code></pre><p>Bekijk de complete code op <a href="https://github.com/python-cursus-nl/wikitest/blob/932dfa1fc94e5e28e52f14b77474595f3934bc1e/toets.py#L9">Github</a></p><h2>De toets afnemen</h2><p>Je hebt nu de code om een Wikpedia-pagina in te lezen en de code om hiervan met behulp van AI een samenvatting en een toets van te maken. De laatste stap is het daadwerkelijk afnemen van de toets. Voor dit voorbeeld kun je het eenvoudig houden door de <code>input</code>-functie te gebruiken. Uiteraard zou je het uitgebreider kunnen maken door er bijvoorbeeld een webapplicatie van te maken.</p><p>Maak hiervoor een functie <code>take_test</code> in het bestand <code>toets.py</code>. In vereenvoudigde vorm ziet het eerste deel van deze functie er als volgt uit:</p><pre><code><code>def take_test():
    print("Geef de titel op (haal dit uit de url, na '/wiki/'")
    titel = input("Titel: ")

    # Lees de Wikipedia-pagina in
    text = read_wiki(titel=titel)

    # Maak de samenvatting en toets
    result = create_test(text=text)

    # Zet om van JSON naar dict
    result_json = json.loads(result)
    samenvatting = result_json.get("samenvatting")
    vragen = result_json.get("vragen")</code></code></pre><p>In een volledige versie gebruik je <code>try/except</code>-blokken om eventuele fouten af te handelen.</p><p>Het laatste deel van de functie:</p><pre><code><code>def take_test():
    # ...

    # Toon samenvatting
    print("\nSAMENVATTING")
    print(samenvatting)

    # Begin de toets. Elke vraag bestaat uit een `dict` met vraag,
    # opties en correct antwoord.
    print("TOETS")
    for vraag in vragen:
        print(f"Vraag: {vraag['vraag']}\n")
        for optie in vraag["opties"]:
            print(optie)

        user_antwoord = input("Wat is je antwoord (A, B of C)? ")
        if user_antwoord.upper() == vraag["antwoord"]:
            print("Goed!\n")
        else:
            print(f"Helaas, het goede antwoord is {vraag['antwoord']}\n")</code></code></pre><p>Toon eerst de samenvatting. Vervolgens itereer je over alle vragen en de antwoordopties en controleer je of de gebruiker hetzelfde antwoord geeft als het goede antwoord.</p><p>Bekijk de complete code op <a href="https://github.com/python-cursus-nl/wikitest/blob/b53c4add7f2bd33ee1d3920cc25ca4977af8a899/toets.py#L74">Github</a></p><p>Nu rest alleen nog de code aan te roepen in <code>main.py</code>:</p><pre><code><code>from dotenv import load_dotenv
from toets import take_test

if __name__ == '__main__':
    load_dotenv()
    take_test()</code></code></pre><p>Gebruik <code>load_dotenv()</code> om je <code>.env</code> bestand met je API-sleutel van OpenAI in te lezen. Vervolgens roep je <code>take_test()</code> aan en ben je klaar om je code uit te proberen!</p><pre><code><code>python main.py

Over welke Wikipediapagina wil je een toets maken?
Geef de titel op (haal dit uit de url, na '/wiki/'
Titel: Guido_van_Rossum

SAMENVATTING
Guido van Rossum is een Nederlandse informaticus, bekend als de ontwerper van de programmeertaal Python. Hij ontving in 1999 de Dr. Dobb's
Journal Excellence in Programming Award en diverse andere accolades voor zijn bijdragen aan de softwareontwikkeling.
Van Rossum woonde in Nederland en sinds 1995 in de VS, en werkte voor bedrijven zoals Google, Dropbox en Microsoft.

TOETS
Vraag: In welk jaar ontwierp Guido van Rossum de programmeertaal Python?

A: 1980
B: 1989
C: 1999
Wat is je antwoord (A, B of C)? b
Goed!

// etc</code></code></pre><h2>Conclusie</h2><p>In deze tutorial heb je een eenvoudig programma gemaakt dat een Wikipedia-pagina inleest en er vervolgens met behulp van de OpenAI API een samenvatting van maakt en er een toets over maakt. Het is een eenvoudig voorbeeld om te laten zien dat je ook op andere manieren AI-modellen kunt gebruiken dan via een chat-interface.</p><p>Het voordeel is dat je hiermee bepaalde taken kunt automatiseren, dus niet steeds weer je opdrachten in een chat typen! Een ander voordeel is dat je eenvoudig je eigen input mee kunt geven, zoals documenten of inhoud uit spreadsheets. Vervolgens kun je de response weer programmatisch verwerken.</p><p>Dit voorbeeld was met OpenAI, maar bekijk ook eens de API's van andere aanbieders. Veelal hebben ze zelf ook een Python-<em>client</em> en is de werking vrijwel hetzelfde als die van OpenAI. Enkele voorbeelden:</p><ul><li><p><a href="https://mistral.ai/">Mistral</a></p></li><li><p><a href="https://claude.ai">Claude</a></p></li><li><p><a href="https://cloud.google.com/ai/apis">Google</a></p></li><li><p><a href="https://azure.microsoft.com/en-us/products/ai-services">Microsoft</a></p></li></ul><p>De mogelijkheden zijn eindeloos. Veel succes!</p>]]></content:encoded></item><item><title><![CDATA[JSON en Pydantic]]></title><description><![CDATA[Leer in deze tutorial hoe je met de Python-bibliotheek Pydantic gemakkelijk gegevens kunt valideren en er handig mee kunt werken. Perfect als je met JSON en API's aan de slag gaat!]]></description><link>https://pythoncursus.substack.com/p/json-en-pydantic</link><guid isPermaLink="false">https://pythoncursus.substack.com/p/json-en-pydantic</guid><dc:creator><![CDATA[python-cursus.nl]]></dc:creator><pubDate>Wed, 03 Apr 2024 06:56:49 GMT</pubDate><enclosure url="https://substack-post-media.s3.amazonaws.com/public/images/427cbbd5-fbb6-4359-9616-919b1ab0fd54_1024x1024.webp" length="0" type="image/jpeg"/><content:encoded><![CDATA[<p>In <a href="https://python-cursus.nl/blog/werken-met-json/">Werken met JSON</a> leerde je wat JSON is en hoe je er met Python mee kunt werken. Je haalde daar CBS-data op in JSON-formaat, verwerkte dat en toonde het inwoneraantal van Nederland per jaar op je scherm.</p><p>Als je met een API werkt, zullen de gegevens heel vaak in JSON worden opgestuurd. Bijna altijd zul je ook de structuur weten van de JSON, of je het nu ontvangt of opstuurt. En ook bijna altijd zul je als je werkt met JSON Python-objecten naar JSON willen omzetten, of juist van JSON naar Python-objecten. Van Python naar JSON noem je <strong>serialiseren</strong>. Van JSON naar Python noem je <strong>deserialiseren</strong>.</p><p>Als je serialiseert of deserialiseert is het prettig als je weet dat de gegevens voldoen aan jouw verwachting. Stel je verwacht van een API de volgende structuur:</p><pre><code><code>[
   {
      "Naam":"JSON",
      "Type":"Gegevensuitwisselingsformaat",
      "isProgrammeertaal":false
   },
   {
      "Naam":"JavaScript",
      "Type":"Programmeertaal",
      "isProgrammeertaal":true
   }
]
</code></code></pre><p>Zet je dit om naar Python, dan wil je <strong>valideren</strong> dat alle gegevens aanwezig zijn, bijvoorbeeld. Of dat extra gegevens die eventueel meekomen niet in je Python-object terechtkomen. Andersom, als je een Python-object hebt dat je naar een API wilt sturen als JSON, dan is het prettig als je er zeker van kunt zijn dat alle verplichte velden aanwezig zijn en van het juiste <a href="https://python-cursus.nl/blog/typesysteem-en-type-hints/">gegevenstype</a> zijn.</p><p>In deze tutorial leer je daarom werken met <a href="https://docs.pydantic.dev/latest/">Pydantic</a>. In hun eigen woorden:</p><blockquote><p>Pydantic is the most widely used data validation library for Python.</p></blockquote><p>Pydantic is een bibliotheek voor het valideren en serialiseren van Python-objecten. Om dit te bereiken maak je gebruik van de ingebouwde <a href="https://python-cursus.nl/blog/typesysteem-en-type-hints/">type hints</a> van Python. Een voorbeeld zal duidelijk maken hoe het werkt.</p><pre><code><code>from pydantic import BaseModel

# Maak een klasse die overerft van Pydantics BaseModel
class User(BaseModel):
    naam: str  # Naam is verplicht en altijd een str
    leeftijd: int  # Leeftijd is verplicht en altijd een int

# User gegevens in een `dict` 
user_dict = {
    "naam": "Guido",
    "leeftijd": 42
}

# Maak een user object aan door de dict uit te pakken
user = User(**user_dict)
print(user)
# Output: User(naam='Guido', leeftijd=42)

# Je kunt nu attributen aanroepen
print(user.naam)  # Guido

# Je kunt een object ook met argumenten aanmaken
user = User(naam="Guido", leeftijd=42)

# Of met model_validate()
user = User.model_validate(user_dict)

# Alle velden zijn verplicht, ontbreekt er &#233;&#233;n, dan krijg je een ValidationError
user_faux = User(naam="Guido")
# ...
# leeftijd
#   Field required [type=missing, input_value={'naam': 'Guido'}, input_type=dict]


# Of als je een verkeerd type gebruikt
user_faux = User(naam="Guido", leeftijd="vijftig")
# ...
# leeftijd
#   Input should be a valid integer, unable to parse string as an integer [type=int_parsing, input_value='vijftig', input_type=str]

# Zet om in JSON
user_json = user.model_dump_json()
print(user_json)
# Output: {"naam":"Guido","leeftijd":42}
</code></code></pre><p>Je ziet, je kunt Pydantic gebruiken om eenvoudig klassen te maken zodat je altijd zeker weet dat je met de juiste gegevens werkt.</p><h2>Het project</h2><p>Gebruik hetzelfde project als in <a href="https://python-cursus.nl/blog/werken-met-json/">Werken met JSON</a>. In plaats van de gegevens op te slaan als JSON en te verwerken, ga je ze met Pydantic valideren en verwerken. Het resultaat blijft hetzelfde, namelijk per jaar het totale bevolkingsaantal van Nederland:</p><pre><code><code>1950: 10.026.773
1951: 10.200.280
...
2022: 17.590.672
2023: 17.811.291
</code></code></pre><h2>Voordat je begint</h2><p>Ga naar het project, activeer de <a href="https://python-cursus.nl/blog/pip-en-venv/">virtual environment</a> en installeer Pydantic:</p><pre><code><code>cd projects/cbs_json
source .venv/bin/activate  # .venv\Scripts\activate.bat voor Windows
python -m pip install pydantic
python -m pip freeze &gt; requirements.txt
</code></code></pre><h3>JSON-gegevens ophalen en valideren</h3><p>In <code>main.py</code> zijn twee functies aanwezig: <code>get_data</code> en <code>get_total_population</code>. De eerste wijziging maak je in <code>get_data</code>. Met deze functie haal je de JSON-gegevens op van het CBS. De wijziging die je maakt is dat je valideert of de inkomende data voldoet aan wat je verwacht, en je gooit direct weg wat je niet nodig hebt.</p><p>De JSON die je terugkrijgt, ziet er ongeveer zo uit (sterk ingekort):</p><pre><code><code>{
  "odata.metadata": "https://opendata.cbs.nl/ODataApi/OData/85496NED/$metadata#Cbs.OData.WebAPI.TypedDataSet", 
  "value": [{"ID": 0, "Perioden": "1950JJ00", "TotaleBevolking_1": 10026773, "Mannen_2": 4998251}]
}
</code></code></pre><p>Je bent alleen ge&#239;nteresseerd in de waardes in <code>value</code>. Deze waarden staan in een lijst, dus je <code>Response</code>-model kan heel eenvoudig zijn. Maak de volgende klasse aan:</p><pre><code><code>from pydantic import BaseModel
# ...

class Response(BaseModel):
    value: list

# ...
</code></code></pre><p>In <code>get_data</code> schreef je de JSON naar een bestand. Dat vervang je nu door een versie waarin je Pydantic gebruikt.</p><p><strong>Oude versie main.py</strong></p><pre><code><code># ...

def get_data():
    session = requests_cache.CachedSession(expire_after=tonight)
    response = session.get(URL)

    # Als de HTTP-code 400 of hoger is, zal er een uitzondering worden opgeworpen
    response.raise_for_status()
    data = response.json()

    # Sla bestand op
    with open("data.json", "w") as f:
        json.dump(data, f, indent=2)

    return data
</code></code></pre><p><strong>Nieuwe versie main.py</strong></p><pre><code><code>from pydantic import BaseModel, ValidationError

# ...

class Response(BaseModel):
    value: list


def get_data():
    session = requests_cache.CachedSession(expire_after=tonight)
    response = session.get(URL)

    # Als de HTTP-code 400 of hoger is, zal er een uitzondering worden opgeworpen
    response.raise_for_status()

    # Valideer de inkomende JSON, behoud alleen de `values`
    try:
        validated_response = Response.model_validate_json(response.content)
    except ValidationError as e:
        print(e)
        return

    return validated_response
</code></code></pre><p>Met <code>Response.model_validate_json(response.content)</code> zet je de inkomende JSON om naar het <code>Response</code>-object, en je valideert dit direct. Alle velden die niet in <code>Response</code> zijn gedefineerd, worden genegeerd. Je houdt zo dus enkel de lijst <code>value</code> over.</p><p>Print je <code>response.value</code>, dan zie je iets als:</p><pre><code><code>print(validated_response.value)
# [{'ID': 0, 'Perioden': '1950JJ00', 'TotaleBevolking_1': 10026773, 'Mannen_2': 4998251, ....
</code></code></pre><p>Mocht er nu iets niet goed gaan en de data is niet valide, dan werpt Pydantic een <code>ValidationError</code> op. Is dat het geval, geef dan <code>None</code> terug. Anders geef je de <code>validated_response</code> terug.</p><h3>Gegevens verwerken</h3><p>Ook de functie <code>get_total_population</code> kun je wijzigen om met Pydantic te werken. Uit alle data ben je alleen ge&#239;nteresseerd in het jaartal en de totale bevolking. Maak een tweede Pydantic-model aan:</p><pre><code><code># ...

class Data(BaseModel):
    jaar: str
    totale_bevolking: int


# ...
</code></code></pre><p>Het jaartal is een <code>str</code> en de totale bevolking een <code>int</code>.</p><p>Wijzig ook de <code>get_total_population</code>-functie.</p><p><strong>Oude versie main.py</strong></p><pre><code><code># ...

def get_total_population():
    # Open het bestand
    with open("data.json") as f:
        data = json.load(f)  # data is nu een Python object (dict)

    # Bereid een lege lijst voor
    result = []

    # Loop door de rijen in de data
    for row in data["value"]:
        year = row["Perioden"].split("JJ00")[0]  # Schoon het jaartal op
        value = row["TotaleBevolking_1"]

        # Voeg het jaartal en de waarde toe aan de lijst als een tuple
        result.append((year, value))

    return result
</code></code></pre><p><strong>Nieuwe versie main.py</strong></p><pre><code><code># ...

def get_total_population(validated_response):
    # Bereid een lege lijst voor
    result = []

    # Loop door de rijen in de data
    for row in validated_response.value:
        jaar = row["Perioden"].split("JJ00")[0]  # Schoon het jaartal op
        totale_bevolking = row["TotaleBevolking_1"]

        try:
            data = Data(jaar=jaar, totale_bevolking=totale_bevolking)
        except ValidationError:
            print("Geen geldige data, sla rij over")
            continue

        # Voeg het jaartal en de waarde toe aan de lijst als een Data instantie
        result.append(data)

    return result
</code></code></pre><p>Omdat je nu geen JSON uit een bestand meer inleest, zorg je ervoor dat de functie een parameter <code>validated_response</code> ontvangt. Verder itereer je nu over <code>validated_data.value</code> - Dus het veld <code>value</code> van het <code>Response</code>-model. En tot slot valideer je de data (jaar en bevolking) met het Pydantic-model <code>Data</code> dat je zojuist hebt aangemaakt.</p><h3>Samenvoegen</h3><p>Alles is nu klaar. Je kunt nu met <code>get_data</code> de data ophalen en valideren. Is er geldige data, dan ga je verder met <code>get_total_population</code>. Tot slot itereer je over elk item in het resultaat, wat een lijst met <code>Data</code>-objecten is. Dit was eerst een <code>tuple</code> met jaar-bevolking paren. Doordat het nu <code>Data</code>-objecten zijn kun je de velden (attributen) aanroepen, wat het geheel explicieter en dus duidelijker maakt.</p><p><strong>Oude versie main.py</strong></p><pre><code><code># ...

if __name__ == "__main__":
    data = get_data()
    total_population = get_total_population()

    for year, value in total_population:
        print(f"{year}: {value:n}")
</code></code></pre><p><strong>Nieuwe versie main.py</strong></p><pre><code><code># ...

if __name__ == "__main__":
    validated_response = get_data()
    if validated_response:
        total_population = get_total_population(validated_response=validated_response)

        for row in total_population:
            print(f"{row.jaar}: {row.totale_bevolking:n}")
</code></code></pre><p><em>Et voila</em>, hetzelfde resultaat als eerst, maar nu gevalideerd met Pydantic &#233;n handiger om mee te werken omdat je met de namen van attributen kunt werken.</p><h2>Conclusie</h2><p>In deze tutorial heb je de basis geleerd van het werken met Pydantic. Er is nog veel meer mogelijk, maar in de kern gaat het erom dat je gegevens kunt valideren en er eenvoudiger mee kunt werken. Het kan in vele situaties handig zijn, maar zeker ook als je met API's werkt waar je gegevens mee wilt uitwisselen. Bekijk de <a href="https://docs.pydantic.dev/latest/">documentatie</a> van Pydantic om er meer over te leren.</p>]]></content:encoded></item><item><title><![CDATA[Werken met Pythons Typesysteem en Type Hints]]></title><description><![CDATA[In Python zijn er verschillende gegevenstypes, zoals getallen en tekst, maar ook complexere types zoals lijsten en woordenboeken. Elk type bepaalt wat je ermee kunt doen. Met behulp van klassen kan je zelfs je eigen gegevenstypes maken. Python controleert niet op het juist gebruik van deze types, wat voor onverwachte bugs in je code kan zorgen. Gelukkig zijn er Type Hints om je te helpen deze fouten te voorkomen.]]></description><link>https://pythoncursus.substack.com/p/werken-met-pythons-typesysteem-en</link><guid isPermaLink="false">https://pythoncursus.substack.com/p/werken-met-pythons-typesysteem-en</guid><dc:creator><![CDATA[python-cursus.nl]]></dc:creator><pubDate>Tue, 26 Mar 2024 15:57:02 GMT</pubDate><enclosure url="https://substack-post-media.s3.amazonaws.com/public/images/f12753b2-f3b8-42e3-83f9-9e4cf1e0081e_1024x1024.webp" length="0" type="image/jpeg"/><content:encoded><![CDATA[<h2>Inleiding</h2><p>In Python zijn er verschillende gegevenstypes, zoals getallen en tekst, maar ook complexere types zoals lijsten en woordenboeken. Elk type bepaalt wat je ermee kunt doen. Met behulp van klassen kan je zelfs je eigen gegevenstypes maken. Python controleert niet op het juist gebruik van deze types, wat voor onverwachte bugs in je code kan zorgen. Gelukkig zijn er Type Hints om je te helpen deze fouten te voorkomen. In dit artikel leer je wat type hints zijn, maar om het waarom te begrijpen, leer je vooral over wat gegevenstypes zijn en hoe en wanneer Python gegevenstypes controleert.</p><h2>Gegevenstypes</h2><p>Programmeren is het werken met gegevens, en elk gegeven heeft een bepaald type. Je kunt bijvoorbeeld werken met getallen, of met tekst. Afhankelijk van het gegevenstype kun je bepaalde handelingen wel of niet uitvoeren. Met getallen kun je rekenen, met tekst niet. Getallen en tekst zullen vrij intu&#239;tief aanvoelen, daar heb je in het dagelijkse leven ook mee te maken. Maar er zijn ook complexere gegevenstypes. In Python heb je bijvoorbeeld de <code>list</code>, een lijst met verschillende elementen. Of de <code>dict</code>, een container met sleutel-waarden paren.</p><pre><code><code>geheel_getal = 1  # int
tekst = "Lorem Ipsum"  # str
mijn_lijst = [1, 2, 3, 4, 5]  # list

# dict
mijn_dict = { 
    "naam": "Guido",
    "rol": "Bedenker van Python",
    "level": 10
}
</code></code></pre><p>In bovenstaand voorbeeld maak je eerst een geheel getal aan. Dit is het gegevenstype <code>int</code> (van integer). Daarna maak je een tekst aan, dit heeft het gegevenstype <code>str</code> (van <em>string</em>). Het derde voorbeeld is een <code>list</code> en tot slot maak je een <code>dict</code> aan, kort voor <em>dictionary</em>, ofwel: woordenboek.</p><p>Naast de ingebouwde gegevenstypes zoals <code>int</code>, <code>str</code>, <code>float</code> en <code>dict</code> kun je ook je eigen gegevenstypes maken, door <strong>klassen</strong> te maken. De <code>dict</code> van hierboven zou je ook kunnen vormgeven als:</p><pre><code><code># Maak een klasse `User` aan
class User:

    def __init__(self, naam, rol, level):
        self.naam = naam
        self.rol = rol
        self.level = level

    def level_up(self):
        self.level += 1


# Maak een instantie van het type `User` aan
guido = User(naam="Guido", rol="Bedenker Python", level=10)
print(type(guido))  #  &lt;class '__main__.User'&gt;

# Level up!
guido.level_up()
print(guido.level)  # 11
</code></code></pre><p>De klasse <code>User</code> bepaalt de gegevens van het type (naam, rol, level) en wat je ermee kunt (een level omhoog gaan, in dit geval).</p><p>Het belangrijkste om te onthouden is: <strong>Een gegevenstype bepaalt wat je met het gegeven kunt doen.</strong></p><blockquote><p>Lees ook het hoofdstuk <a href="https://basis.python-cursus.nl/eerste-stappen/">Eerste stappen</a> uit onze gratis Basiscursus Python om meer te leren over gegevenstypen in Python.</p></blockquote><blockquote><p>Lees het hoofdstuk <a href="https://basis.python-cursus.nl/werken-met-klassen/">Werken met klassen</a> uit onze gratis Basiscursus Python voor meer informatie over het werken met klassen in Python.</p></blockquote><h2>Typesystemen</h2><p>Als je met gegevens - en dus met gegevenstypes - werkt, kunnen er verschillende dingen verkeerd gaan die specifiek met het type te maken hebben. Stel dat je de volgende functie hebt:</p><pre><code><code>def hoofdletters(woord):
    """Zet het gegeven woord om in kapitalen"""

    woord_in_kapitalen = woord.upper()   
    return woord_in_kapitalen
</code></code></pre><p>Een eenvoudige functie, die de methode <code>upper</code> gebruikt om het woord in kapitalen om te zetten. Maar... <code>upper</code> is een methode die je alleen op tekst (<code>str</code>) kunt toepassen. Het volgende levert dus geen problemen op:</p><pre><code><code>woord = hoofdletters(woord="test")
print(woord)  # TEST
</code></code></pre><p>Maar wat als je nu geen <code>str</code> opgeeft, maar een <code>int</code>?</p><pre><code><code>getal = hoofdletters(woord=10)

# AttributeError: 'int' object has no attribute 'upper'
</code></code></pre><p>Je ziet dat als je een verkeerd gegevenstype opgeeft als argument, er een fout ontstaat. Een <code>int</code> heeft namelijk geen methode <code>upper</code>!</p><p>Voordat je leert hoe je dit kunt voorkomen, is het belangrijk om te begrijpen hoe verschillende talen omgaan met het controleren van gegevenstypes. Hoe een taal dit implementeert noem je een <strong>typesysteem</strong>. Je kunt typesystemen langs twee assen indelen:</p><ul><li><p>Dynamisch of Statisch</p></li><li><p>Zwak of Sterk</p></li></ul><h3>Dynamisch of Statisch</h3><p>De eerste as is of een taal gebruik maakt van <em>dynamische</em> of <em>statische</em> typering. Een taal is statisch getypeerd als het type van een <strong>variabele</strong> tijdens <strong>compileren</strong> bekend is. Het compileren is het omzetten van de hogere taal (bijvoorbeeld C++) naar een taal die de computer begrijpt (machinetaal of bytecode). Is de taal statisch getypeerd, dan moet elke variabele een type hebben <em>tijdens het compileren</em>. Je ziet statische typering met name - maar niet uitsluitend - bij talen die gecompileerd worden <em>voordat</em> ze uitgevoerd kunnen worden.</p><p>Een dynamisch getypeerde taal daarentegen, associeert gegevenstypen met de <strong>waarden</strong> tijdens de <strong>uitvoering</strong> van het programma. Vaak - maar niet uitsluitend - is dit bij <em>ge&#239;nterpreteerde</em> talen. Een ge&#239;nterpreteerde taal voert de broncode regel voor regel uit en zet het <em>tijdens uitvoer</em> om in machinetaal of bytecode.</p><blockquote><p>Compileren en interpreteren zijn ingewikkelde concepten, met veel nuances. Het geeft niet als je het niet 100% begrijpt. Belangrijk is dat je begrijpt dat dynamisch getypeerde talen <em>tijdens uitvoer</em> de types controleren. Statische getypeerde talen doen dit bij <em>compilatie</em>.</p></blockquote><p>Python is een dynamisch getypeerde taal. Je kunt daarom gerust bovenstaande functie <code>hoofdletters</code> defini&#235;ren, en er pas tijdens uitvoering achter komen dat er een fout ontstaat als er een <code>int</code> wordt opgegeven als argument.</p><p>In een statisch getypeerde taal zou je tijdens het compileren - dus v&#243;&#243;r uitvoering - er al achter komen dat er een mogelijke fout is, omdat de <em>compiler</em> weet dat je een <code>int</code> niet in hoofdletters kunt omzetten. Deze <em>bug</em> haal je er dan dus al uit voordat je code wordt uitgevoerd.</p><h3>Zwak of Sterk</h3><p>Een tweede as waarop je het typesysteem kunt indelen is of het sterke of zwakke typering betreft. In een zwak getypeerde taal - zoals JavaScript - is onderstaande mogelijk:</p><pre><code><code>10 + "10"
// "1010"
</code></code></pre><p>Het resultaat is een tekst, het getal is dus impliciet omgezet van getal naar tekst.</p><p>In een sterk getypeerde taal zou dit een fout opleveren, daar kun je (sommige) gegevenstypes alleen expliciet omzetten:</p><pre><code><code>x = 10 + int("10")
print(x)  # 20
</code></code></pre><p>Python is een sterk getypeerde taal.</p><h2>Introducing: Type Hints</h2><p>Python is een <strong>dynamisch getypeerde</strong> en <strong>ge&#239;nterpreteerde</strong> taal. Een voordeel hiervan is dat je er snel in kunt ontwikkelen. Je hoeft niet steeds eerst te compileren voordat je uitvoert, en je hoeft je ook niet druk te maken om gegevenstypes. Het nadeel hiervan is dat Python over het algemeen langzamer is dan gecompileerde talen zoals C, C++ of Rust. Een ander nadeel zag je al: <em>bugs</em> die te maken hebben met gegevenstypes komen soms pas tijdens uitvoering naar boven.</p><p>Sinds Python 3 is hier gelukkig een oplossing voor: <strong>Type Hints</strong>. Type hints zijn <em>optionele</em> toevoegingen aan je code waarmee je aangeeft van welk type je variabelen, parameters en <em>return values</em> zijn. Hiermee wordt het mogelijk om met behulp van externe tools je programma <em>statisch</em> te controleren. Hiermee haal je potenti&#235;le <em>bugs</em> dus al uit je code voordat het uitgevoerd wordt. Het eerdere voorbeeld kun je herschrijven als:</p><pre><code><code>def hoofdletters(woord: str) -&gt; str:
    """Zet het gegeven woord om in kapitalen"""

    woord_in_kapitalen = woord.upper()   
    return woord_in_kapitalen

woord = hoofdletters("test")
getal = hoofdletters(10)
</code></code></pre><p>Op de eerste regel zie je dat de parameter nu verwacht van het type <code>str</code> te zijn, aangegeven met <code>woord: str</code>. Met <code>-&gt; str</code> geef je aan dat de functie altijd een <code>str</code> teruggeeft.</p><p>Je kunt nu bijvoorbeeld <a href="https://mypy-lang.org/">mypy</a> gebruiken om je code te controleren, <em>v&#243;&#243;rdat je de code publiceert / uitvoert</em>.</p><pre><code><code>mypy main.py

main.py:9: error: Argument 1 to "hoofdletters" has incompatible type "int"; expected "str"
Found 1 error in 1 file (checked 1 source file)
</code></code></pre><p>Deze type hints zijn optioneel en worden genegeerd door de <em>interpreter</em>. Je kunt ze dus stapsgewijs toevoegen in je bestaande code, de ene functie annoteer je wel, de andere niet.</p><p>Overigens is het belangrijk om te onthouden dat je met type hints niet alle <em>bugs</em> opspoort. Stel dat je bovenstaande functie gebruikt met invoer van een gebruik, bijvoorbeeld via een formulier. Die invoer moet je dan nog steeds controleren! Voert de gebruiker een cijfer in, dan zal er nog steeds een fout ontstaan.</p><h3>Voorbeelden</h3><p>Het type hints systeem is erg uitgebreid, zie de <a href="https://mypy.readthedocs.io/en/stable/cheat_sheet_py3.html">cheatsheet</a> voor een snel overzicht. Hier volgen een paar voorbeelden om je op gang te helpen.</p><pre><code><code># Type hint variabelen
woord: str = "Test"
level: int = 10

# Type hints voor `dicts`
mijn_dict: dict[str, int]

# Of als de waarden van verschillende waarden kunnen zijn
from typing import Union

mijn_dict: dict[str, Union[str, int]]

# Of hetzelfde, vanaf Python 3.10
mijn_dict: dict[str, str | int]

# Gebruik Optional voor waarden die `None` mogen zijn
from typing import Optional
x: Optional[str] = None

# Of hetzelfde, vanaf Python 3.10
x: str | None = None

# Gebruik een eigen klasse als type
class User:
    def __init__(self, email):
        self.email = email

def email_user(user: User) -&gt; None:
    # E-mail user
    print(f"E-mail verstuurd naar {user.email}")
</code></code></pre><h2>Conclusie</h2><p>Type hints in Python bieden het beste van beide werelden - de flexibiliteit en snelheid van dynamische typering, aangevuld met meer controle en zekerheid door de optionele type hints. Een bijkomend voordeel is dat veel Python IDEs en tools de type hints gebruiken om betere code-analyse en autocomplete te bieden.</p><p>Je kunt eenvoudig met type hints beginnen in je projecten, doordat optioneel zijn. Het is prima om sommige stukjes code wel te annoteren, en andere niet. Installeer bijvoorbeeld <code>mypy</code>, en je kunt beginnen!</p>]]></content:encoded></item><item><title><![CDATA[Werken met pandas]]></title><description><![CDATA[Ontdek hoe je met `pandas` in Python jouw data kunt analyseren en bewerken. Je leert CSV's inladen en bewerken en voert een eenvoudige analyse uit.]]></description><link>https://pythoncursus.substack.com/p/werken-met-pandas</link><guid isPermaLink="false">https://pythoncursus.substack.com/p/werken-met-pandas</guid><dc:creator><![CDATA[python-cursus.nl]]></dc:creator><pubDate>Thu, 21 Mar 2024 07:41:23 GMT</pubDate><enclosure url="https://substackcdn.com/image/fetch/f_auto,q_auto:good,fl_progressive:steep/https%3A%2F%2Fsubstack-post-media.s3.amazonaws.com%2Fpublic%2Fimages%2F2c593e4c-3c52-4b0c-8ded-261758320eac_541x394.svg" length="0" type="image/jpeg"/><content:encoded><![CDATA[<h1>Werken met pandas, een introductie</h1><p>In een <a href="https://python-cursus.nl/blog/geautomatiseerd-gegevens-downloaden/">Geautomatiseerd gegevens downloaden</a> heb je geleerd hoe je met <code>requests</code> data kunt ophalen van een URL. In deze tutorial ga je een stapje verder en ga je aan de slag met die data. Je leert werken met het pakket <a href="https://pandas.pydata.org/">pandas</a>, een veelgebruikte tool voor data-analyse in Python.</p><h2>Wat is pandas?</h2><p><code>pandas</code> is een open-source pakket om data-analyses uit te voeren en data te bewerken. De basis is de zogenaamde <code>DataFrame</code>. Dit is een tabel met rijen en kolommen, vergelijkbaar met een spreadsheet:</p><div class="captioned-image-container"><figure><a class="image-link image2 is-viewable-img" target="_blank" href="https://substackcdn.com/image/fetch/$s_!-ske!,f_auto,q_auto:good,fl_progressive:steep/https%3A%2F%2Fsubstack-post-media.s3.amazonaws.com%2Fpublic%2Fimages%2F2c593e4c-3c52-4b0c-8ded-261758320eac_541x394.svg" data-component-name="Image2ToDOM"><div class="image2-inset"><picture><source type="image/webp" srcset="https://substackcdn.com/image/fetch/$s_!-ske!,w_424,c_limit,f_webp,q_auto:good,fl_progressive:steep/https%3A%2F%2Fsubstack-post-media.s3.amazonaws.com%2Fpublic%2Fimages%2F2c593e4c-3c52-4b0c-8ded-261758320eac_541x394.svg 424w, https://substackcdn.com/image/fetch/$s_!-ske!,w_848,c_limit,f_webp,q_auto:good,fl_progressive:steep/https%3A%2F%2Fsubstack-post-media.s3.amazonaws.com%2Fpublic%2Fimages%2F2c593e4c-3c52-4b0c-8ded-261758320eac_541x394.svg 848w, https://substackcdn.com/image/fetch/$s_!-ske!,w_1272,c_limit,f_webp,q_auto:good,fl_progressive:steep/https%3A%2F%2Fsubstack-post-media.s3.amazonaws.com%2Fpublic%2Fimages%2F2c593e4c-3c52-4b0c-8ded-261758320eac_541x394.svg 1272w, https://substackcdn.com/image/fetch/$s_!-ske!,w_1456,c_limit,f_webp,q_auto:good,fl_progressive:steep/https%3A%2F%2Fsubstack-post-media.s3.amazonaws.com%2Fpublic%2Fimages%2F2c593e4c-3c52-4b0c-8ded-261758320eac_541x394.svg 1456w" sizes="100vw"><img src="https://substackcdn.com/image/fetch/$s_!-ske!,w_1456,c_limit,f_auto,q_auto:good,fl_progressive:steep/https%3A%2F%2Fsubstack-post-media.s3.amazonaws.com%2Fpublic%2Fimages%2F2c593e4c-3c52-4b0c-8ded-261758320eac_541x394.svg" width="1456" height="1062" data-attrs="{&quot;src&quot;:&quot;https://substack-post-media.s3.amazonaws.com/public/images/2c593e4c-3c52-4b0c-8ded-261758320eac_541x394.svg&quot;,&quot;srcNoWatermark&quot;:null,&quot;fullscreen&quot;:null,&quot;imageSize&quot;:null,&quot;height&quot;:1062,&quot;width&quot;:1456,&quot;resizeWidth&quot;:null,&quot;bytes&quot;:null,&quot;alt&quot;:&quot;DataFrame&quot;,&quot;title&quot;:null,&quot;type&quot;:null,&quot;href&quot;:null,&quot;belowTheFold&quot;:false,&quot;topImage&quot;:true,&quot;internalRedirect&quot;:null,&quot;isProcessing&quot;:false,&quot;align&quot;:null,&quot;offset&quot;:false}" class="sizing-normal" alt="DataFrame" title="DataFrame" srcset="https://substackcdn.com/image/fetch/$s_!-ske!,w_424,c_limit,f_auto,q_auto:good,fl_progressive:steep/https%3A%2F%2Fsubstack-post-media.s3.amazonaws.com%2Fpublic%2Fimages%2F2c593e4c-3c52-4b0c-8ded-261758320eac_541x394.svg 424w, https://substackcdn.com/image/fetch/$s_!-ske!,w_848,c_limit,f_auto,q_auto:good,fl_progressive:steep/https%3A%2F%2Fsubstack-post-media.s3.amazonaws.com%2Fpublic%2Fimages%2F2c593e4c-3c52-4b0c-8ded-261758320eac_541x394.svg 848w, https://substackcdn.com/image/fetch/$s_!-ske!,w_1272,c_limit,f_auto,q_auto:good,fl_progressive:steep/https%3A%2F%2Fsubstack-post-media.s3.amazonaws.com%2Fpublic%2Fimages%2F2c593e4c-3c52-4b0c-8ded-261758320eac_541x394.svg 1272w, https://substackcdn.com/image/fetch/$s_!-ske!,w_1456,c_limit,f_auto,q_auto:good,fl_progressive:steep/https%3A%2F%2Fsubstack-post-media.s3.amazonaws.com%2Fpublic%2Fimages%2F2c593e4c-3c52-4b0c-8ded-261758320eac_541x394.svg 1456w" sizes="100vw" fetchpriority="high"></picture><div class="image-link-expand"><div class="pencraft pc-display-flex pc-gap-8 pc-reset"><button tabindex="0" type="button" class="pencraft pc-reset pencraft icon-container restack-image"><svg aria-hidden="true" width="20" height="20" viewBox="0 0 20 20" fill="none" stroke-width="1.5" stroke="var(--color-fg-primary)" stroke-linecap="round" stroke-linejoin="round" xmlns="http://www.w3.org/2000/svg"><g><path d="M2.53001 7.81595C3.49179 4.73911 6.43281 2.5 9.91173 2.5C13.1684 2.5 15.9537 4.46214 17.0852 7.23684L17.6179 8.67647M17.6179 8.67647L18.5002 4.26471M17.6179 8.67647L13.6473 6.91176M17.4995 12.1841C16.5378 15.2609 13.5967 17.5 10.1178 17.5C6.86118 17.5 4.07589 15.5379 2.94432 12.7632L2.41165 11.3235M2.41165 11.3235L1.5293 15.7353M2.41165 11.3235L6.38224 13.0882"></path></g></svg></button><button tabindex="0" type="button" class="pencraft pc-reset pencraft icon-container view-image"><svg xmlns="http://www.w3.org/2000/svg" width="20" height="20" viewBox="0 0 24 24" fill="none" stroke="currentColor" stroke-width="2" stroke-linecap="round" stroke-linejoin="round" class="lucide lucide-maximize2 lucide-maximize-2"><polyline points="15 3 21 3 21 9"></polyline><polyline points="9 21 3 21 3 15"></polyline><line x1="21" x2="14" y1="3" y2="10"></line><line x1="3" x2="10" y1="21" y2="14"></line></svg></button></div></div></div></a></figure></div><p>Een kolom in <code>pandas</code> is van het gegevenstype <code>Series</code>. De donkergrijze rij bevat de kolomnamen en de donkergrijze eerste kolom bevat de index (rijnummers of namen).</p><h2>Waarom pandas?</h2><p>Als je te maken hebt met grote hoeveelheden data in de vorm van CSV-bestanden, Excel-sheets, databases en meer is het prettig om deze data snel en effici&#235;nt te kunnen analyseren en bewerken. Dit is waar <code>pandas</code> goed in is. Met <code>pandas</code> kun je:</p><ul><li><p>Data importeren/exporteren in veel verschillende formaten (CSV, Excel, SQL, etc.)</p></li><li><p>Data opschonen (Missende waarden vervangen, kolommen hernoemen, rijen verwijderen, etc.)</p></li><li><p>Data analyseren (groeperen, samenvatten, statistieken berekenen, etc.)</p></li></ul><p>Het grote voordeel van <code>pandas</code> ten opzichte van bijvoorbeeld Excel, is dat het eenvoudiger is om het werken met gegevens te automatiseren. Heb je een Excelbestand waar je eenmalig iets uit nodig hebt, maak dan vooral een draaitabel.</p><p>Maar krijg je elke week hetzelfde Excelbestand met nieuwe gegevens, en ben je elke keer die draaitabel opnieuw aan het maken, dan is <code>pandas</code> een uitkomst. Daar komt bij dat het goed in te passen is in een volledig (Python)programma: inlezen van gegevens, opschonen, analyses maken, een rapport maken en dit e-mailen. En dat allemaal automatisch!</p><h2>Het project</h2><p>Vervolg op 'Geautomatiseerd gegevens downloaden'</p><p>Dit project is een vervolg op <a href="https://python-cursus.nl/blog/geautomatiseerd-gegevens-downloaden">Geautomatiseerd gegevens downloaden</a>. Als je dat nog niet hebt gedaan, lees (en maak) dat dan eerst.</p><p>In dat project download je data van het KNMI. Dit komt in een txt-bestand. Nadat je het tekstbestand hebt opgeslagen, schoon je het op en sla je het op als CSV-bestand, zodat er eenvoudiger mee is te werken. De data zijn maand- en jaargemiddelde temperaturen van het meetstation De Bilt.</p><p>In dit vervolgproject laad je KNMI-gegevens die je in een CSV hebt opgeslagen in met <code>pandas</code> in om er vervolgens een eenvoudige analyses op los te laten. Je kunt verdergaan in het eerdere KNMI-project. Activeer de virtual environment en installeer <code>pandas</code>:</p><pre><code><code>cd projects/knmi
source .venv/bin/activate  # .venv\Scripts\activate.bat voor Windows
python -m pip install pandas
python -m pip freeze &gt; requirements.txt
</code></code></pre><h2>Aan de slag met pandas</h2><p>Zodra <code>pandas</code> ge&#239;nstalleerd is, kun je het importeren. Het is gebruikelijk om <code>pandas</code> te importeren onder de alias <code>pd</code>. Voeg vervolgens een functie <code>load_data</code> toe aan het project. Dit kan in <code>main.py</code>, maar omdat het project wat uitgebreider wordt kun je het ook in een apart bestand plaatsen. Bijvoorbeeld in <code>data.py</code>. Onder de code lees je de toelichting.</p><pre><code><code># ...

import pandas as pd

# ...

CSV = "knmi.csv"
KNMI_COLUMNS = {
    "STN": "Station",
    "JAN": "Januari",
    # ... etc
    "DEC": "December",
    "YYYY": "Jaar",
    "YEAR": "Jaargemiddelde",
}

MONTH_COLUMNS = []
for key, value in KNMI_COLUMNS.items():
    if key not in ["STN", "YYYY", "YEAR"]:
        MONTH_COLUMNS.append(value)

def load_data():
    """Laad de gegevens uit het CSV-bestand in een pandas DataFrame en maak enkele aanpassingen."""

    # Maak een DataFrame van het CSV-bestand
    df = pd.read_csv(CSV, delimiter=",")

    # Hernoem de kolommen
    df.rename(columns=KNMI_COLUMNS, inplace=True)

    # Van 0.1 &#176;C naar 1&#176;C
    columns = MONTH_COLUMNS + ["Jaargemiddelde"]
    updated_columns = df[columns].map(lambda x: x / 10, na_action="ignore")
    df[columns] = updated_columns

    # Toon de eerste 5 rijen voor een snelle inspectie
    print(df.head(), "\n")

    return df
</code></code></pre><p>In deze functie laad je de gegevens uit de eerder gemaakte CSV in, hernoem je de kolommen en wijzig je de getallen van 0.1 &#176;C naar 1&#176;C (dus 80.5 wordt 8.5). Tot slot gebruik je <code>df.head()</code> om de <code>DataFrame</code> snel te inspecteren.</p><p><strong>CSV inlezen</strong></p><p>Met <code>pandas</code> kun je eenvoudig gegevens inlezen uit verschillende soorten bestanden, zoals CSV, Excel, JSON, etc. Voor elk bestandsformaat is er de methode <code>read_*</code>. Dus om het CSV-bestand in te lezen gebruik je <code>pd.read_csv()</code>. Je geeft altijd het pad naar het bestand op (in dit geval opgeslagen in de constante <code>CSV</code>) en optioneel andere argumenten. Omdat de KNMI-gegevens zijn opgeslagen in een CSV waarbij de waarden worden gescheiden door een komma, gebruik je <code>delimiter=","</code>. De gegevens worden ingelezen en opgeslagen in de variabele <code>df</code>, die van het type <code>DataFrame</code> is.</p><p>Zie <a href="https://pandas.pydata.org/pandas-docs/stable/reference/api/pandas.read_csv.html">de documentatie van read_csv</a> voor alle opties.</p><p><strong>Kolommen hernoemen</strong></p><p>De kolomnamen bestaan uit Engelse afkortingen, het is mooier voor het resultaat om te werken met de volledige Nederlandse namen. Gebruik <code>df.rename()</code> om dit te doen. Met het argument <code>columns</code> geef je aan welke kolommen je wilt wijzigen. Je geeft een <code>dict</code> mee met de oude en nieuwe namen als <em>key-value pairs</em>. Met <code>inplace=true</code> geef je aan dat <code>df</code> gewijzigd wordt, in plaats van dat er een nieuwe <code>DataFrame</code> wordt teruggegeven.</p><p><strong>Van 0.1 &#176;C naar 1&#176;C</strong></p><p>De gegevens in het oorspronkelijke formaat zijn in tienden van graden. Dus een gemiddelde temperatuur van 8 graden, staat genoteerd als 80. Omdat we meer gewend zijn in graden te denken, pas je dit aan. Hiervoor maak je eerst een lijst met alle maandnamen, plus de kolom "Jaargemiddelde". Vervolgens wijzig je alle gegevens in die kolommen. Hiervoor gebruik je de volgende code:</p><pre><code><code>columns = MONTH_COLUMNS + ["Jaargemiddelde"]
updated_columns = df[columns].map(lambda x: x / 10, na_action="ignore")
df[columns] = updated_columns
</code></code></pre><p>De syntax om een kolom te selecteren is <code>df["kolomnaam"]</code> voor een enkele kolom, of <code>df[["kolomnaam1", "kolomnaam2"]]</code> voor meerdere kolommen. Met <code>df[columns]</code> selecteer je dus alle kolommen met meetgegevens. Vervolgens pas je de methode <code>.map()</code> toe. Deze methode ontvangt een functie als argument die wordt toegepast op elke cel in de kolommen.</p><p>Omdat je alle getallen wilt delen door 10, gebruik je <code>map(lambda x: x / 10)</code>. Dit ziet er ingewikkeld uit, maar is hetzelfde als:</p><pre><code><code>def deel_door_10(x):
    return x / 10

df[columns].map(deel_door_10, na_action="ignore")
</code></code></pre><p><code>lambda x: x / 10</code> is een zogenaamde lambda-functie, ofwel een korte functie zonder het eerst te hoeven defini&#235;ren.</p><p>Met <code>na_action="ignore"</code> geef je aan dat als een cel geen waarde bevat, de functie wordt overgeslagen (anders krijg je fouten).</p><p>Je hebt nu een variabele <code>updated_columns</code> met alle aangepaste kolommen. De laatste stap is om de oude kolommen in <code>df</code> te vervangen door de nieuwe kolommen:</p><pre><code><code>df[columns] = updated_columns
</code></code></pre><p><strong>Inspecteren</strong></p><p>Tot slot print je de eerste vijf rijen van de <code>DataFrame</code> met <code>df.head()</code>. Dit is handig om snel te kijken hoe je data eruit ziet. Het resultaat ziet er ongeveer zo uit:</p><div class="captioned-image-container"><figure><a class="image-link image2" target="_blank" href="https://substackcdn.com/image/fetch/$s_!Jjdy!,f_auto,q_auto:good,fl_progressive:steep/https%3A%2F%2Fsubstack-post-media.s3.amazonaws.com%2Fpublic%2Fimages%2F59fdf04b-e690-4042-882c-da42528bbfdf_550x190.webp" data-component-name="Image2ToDOM"><div class="image2-inset"><picture><source type="image/webp" srcset="https://substackcdn.com/image/fetch/$s_!Jjdy!,w_424,c_limit,f_webp,q_auto:good,fl_progressive:steep/https%3A%2F%2Fsubstack-post-media.s3.amazonaws.com%2Fpublic%2Fimages%2F59fdf04b-e690-4042-882c-da42528bbfdf_550x190.webp 424w, https://substackcdn.com/image/fetch/$s_!Jjdy!,w_848,c_limit,f_webp,q_auto:good,fl_progressive:steep/https%3A%2F%2Fsubstack-post-media.s3.amazonaws.com%2Fpublic%2Fimages%2F59fdf04b-e690-4042-882c-da42528bbfdf_550x190.webp 848w, https://substackcdn.com/image/fetch/$s_!Jjdy!,w_1272,c_limit,f_webp,q_auto:good,fl_progressive:steep/https%3A%2F%2Fsubstack-post-media.s3.amazonaws.com%2Fpublic%2Fimages%2F59fdf04b-e690-4042-882c-da42528bbfdf_550x190.webp 1272w, https://substackcdn.com/image/fetch/$s_!Jjdy!,w_1456,c_limit,f_webp,q_auto:good,fl_progressive:steep/https%3A%2F%2Fsubstack-post-media.s3.amazonaws.com%2Fpublic%2Fimages%2F59fdf04b-e690-4042-882c-da42528bbfdf_550x190.webp 1456w" sizes="100vw"><img src="https://substackcdn.com/image/fetch/$s_!Jjdy!,w_1456,c_limit,f_auto,q_auto:good,fl_progressive:steep/https%3A%2F%2Fsubstack-post-media.s3.amazonaws.com%2Fpublic%2Fimages%2F59fdf04b-e690-4042-882c-da42528bbfdf_550x190.webp" width="550" height="190" data-attrs="{&quot;src&quot;:&quot;https://substack-post-media.s3.amazonaws.com/public/images/59fdf04b-e690-4042-882c-da42528bbfdf_550x190.webp&quot;,&quot;srcNoWatermark&quot;:null,&quot;fullscreen&quot;:null,&quot;imageSize&quot;:null,&quot;height&quot;:190,&quot;width&quot;:550,&quot;resizeWidth&quot;:null,&quot;bytes&quot;:null,&quot;alt&quot;:&quot;Inspecteer een DataFrame met .head()&quot;,&quot;title&quot;:null,&quot;type&quot;:null,&quot;href&quot;:null,&quot;belowTheFold&quot;:true,&quot;topImage&quot;:false,&quot;internalRedirect&quot;:null,&quot;isProcessing&quot;:false,&quot;align&quot;:null,&quot;offset&quot;:false}" class="sizing-normal" alt="Inspecteer een DataFrame met .head()" title="Inspecteer een DataFrame met .head()" srcset="https://substackcdn.com/image/fetch/$s_!Jjdy!,w_424,c_limit,f_auto,q_auto:good,fl_progressive:steep/https%3A%2F%2Fsubstack-post-media.s3.amazonaws.com%2Fpublic%2Fimages%2F59fdf04b-e690-4042-882c-da42528bbfdf_550x190.webp 424w, https://substackcdn.com/image/fetch/$s_!Jjdy!,w_848,c_limit,f_auto,q_auto:good,fl_progressive:steep/https%3A%2F%2Fsubstack-post-media.s3.amazonaws.com%2Fpublic%2Fimages%2F59fdf04b-e690-4042-882c-da42528bbfdf_550x190.webp 848w, https://substackcdn.com/image/fetch/$s_!Jjdy!,w_1272,c_limit,f_auto,q_auto:good,fl_progressive:steep/https%3A%2F%2Fsubstack-post-media.s3.amazonaws.com%2Fpublic%2Fimages%2F59fdf04b-e690-4042-882c-da42528bbfdf_550x190.webp 1272w, https://substackcdn.com/image/fetch/$s_!Jjdy!,w_1456,c_limit,f_auto,q_auto:good,fl_progressive:steep/https%3A%2F%2Fsubstack-post-media.s3.amazonaws.com%2Fpublic%2Fimages%2F59fdf04b-e690-4042-882c-da42528bbfdf_550x190.webp 1456w" sizes="100vw" loading="lazy"></picture><div></div></div></a></figure></div><h2>Een analyse uitvoeren</h2><p>Je hebt gezien dat je met <code>pandas</code> data kunt inladen en transformeren. Je kunt nu een (eenvoudige) analyse maken, bijvoorbeeld het ophalen van de hoogste jaargemiddelde temperatuur:</p><pre><code><code>def get_highest_mean_temp(df):
    """
    Haal de hoogste jaargemiddelde temperatuur op.
    """

    # Haal de hoogste jaargemiddelde temperatuur op
    highest_mean_temp = df["Jaargemiddelde"].max()  # Temperatuur in &#176;C
    index = df["Jaargemiddelde"].idxmax()  # Index van de rij met de hoogste temperatuur
    year = df.at[index, "Jaar"]  # Jaar van de hoogste temperatuur

    print(f"Hoogste jaargemiddelde: {highest_mean_temp}&#176;C")
    print(f"Dit was in jaar: {year}")

    return highest_mean_temp, year
</code></code></pre><p>Eerst selecteer je de juiste kolom met <code>df["Jaargemiddelde"]</code>. Vervolgens haal je hier het hoogste getal uit op met <code>.max()</code>. Omdat je ook het bijbehorende jaar wilt weten, doe je hetzelfde nog een keer, maar nu met <code>.idmax()</code>. Dit haalt de index op van de rij met het hoogste getal.</p><p>Je kunt nu het jaar ophalen met <code>df.at[]</code>, waarbij je de zojuist opgehaalde index als eerste waarde opgeeft, en de kolomnaam "Jaar" als tweede waarde. Zo selecteer je de cel uit de rij met de hoogste temperatuur de kolom "Jaar".</p><h2>Conclusie</h2><p><code>pandas</code> is een krachtige tool voor data-analyse in Python. Het biedt veel mogelijkheden waarmee je data kunt importeren, exporteren, opschonen en analyseren. In deze tutorial heb je het topje van de ijsberg gezien van wat mogelijk is. Werk je veel met spreadsheet-achtige gegevens (zoals CSV of Excel), dan is <code>pandas</code> alleen al handig voor het opschonen en transformeren van deze gegevens.</p>]]></content:encoded></item><item><title><![CDATA[Cache je HTTP requests]]></title><description><![CDATA[Leer hoe je met de Python library 'requests-cache' HTTP-responses opslaat in een cache, zodat je code effici&#235;nter werkt.]]></description><link>https://pythoncursus.substack.com/p/cache-je-http-requests</link><guid isPermaLink="false">https://pythoncursus.substack.com/p/cache-je-http-requests</guid><dc:creator><![CDATA[python-cursus.nl]]></dc:creator><pubDate>Tue, 19 Mar 2024 07:43:17 GMT</pubDate><enclosure url="https://substack-post-media.s3.amazonaws.com/public/images/18cca24d-9c3c-46f0-8be9-b3366c610683_1024x1024.webp" length="0" type="image/jpeg"/><content:encoded><![CDATA[<p>In een aantal eerdere tutorials heb je al geleerd over <code>requests</code>, een Python library om HTTP-verzoeken mee te maken. Bijvoorbeeld in <a href="https://python-cursus.nl/blog/werken-met-json/">Werken met JSON</a> kwam dit aan de orde. In dat voorbeeld download je data van het CBS met een functie <code>get_data</code> en sla je het op in een bestand. Roep je de functie nogmaals aan met <code>refresh=False</code>, dan download je de data niet nogmaals van het CBS, maar lees je het direct uit het bestand. De functie ziet er zo uit:</p><pre><code><code>import json
import requests

URL = "https://opendata.cbs.nl/ODataApi/odata/85496NED/TypedDataSet"

def get_data(refresh=True):
    if not refresh:
        try:
            with open("data.json") as f:
                data = json.load(f)
        except FileNotFoundError:
            print("Bestand niet gevonden, downloaden")

            # Roep deze functie aan, met `refresh=True`
            return get_data(refresh=True)
    else:
        response = requests.get(URL)

        # Als de HTTP-code 400 of hoger is, zal er een uitzondering worden opgeworpen
        response.raise_for_status()
        data = response.json()

        # Sla bestand op
        with open("data.json", "w") as f:
            json.dump(data, f, indent=2)

    return data
</code></code></pre><p>Dit werkt prima, maar er is een handigere manier. Met <a href="https://requests-cache.readthedocs.io/en/stable/">requests-cache</a> stel je eenvoudig in hoe lang je de HTTP-responses wilt cachen. Vervolgens kun je de code gelijk houden, en zal bijvoorbeeld een <code>GET</code> alleen uitgevoerd worden als de cache verlopen is.</p><p>Bovenstaand voorbeeld kun je eerst herschrijven naar een eenvoudigere versie zonder (handmatige) caching:</p><pre><code><code>import json
import requests

URL = "https://opendata.cbs.nl/ODataApi/odata/85496NED/TypedDataSet"

def get_data():
    response = requests.get(URL)

    # Als de HTTP-code 400 of hoger is, zal er een uitzondering worden opgeworpen
    response.raise_for_status()
    data = response.json()

    # Sla bestand op
    with open("data.json", "w") as f:
        json.dump(data, f, indent=2)

    return data
</code></code></pre><p>Vervolgens voeg je een cache toe met <code>requests-cache</code>.</p><pre><code><code>from datetime import datetime, timedelta
import json
import requests_cache

URL = "https://opendata.cbs.nl/ODataApi/odata/85496NED/TypedDataSet"

now = datetime.now()
tonight = datetime(now.year, now.month, now.day) + timedelta(days=1)

def get_data():
    session = requests_cache.CachedSession(expire_after=tonight)
    response = session.get(URL)

    # Als de HTTP-code 400 of hoger is, zal er een uitzondering worden opgeworpen
    response.raise_for_status()
    data = response.json()

    # Sla bestand op
    with open("data.json", "w") as f:
        json.dump(data, f, indent=2)

    return data
</code></code></pre><p>Boven de functie definieer je eerst <code>tonight</code>, ingesteld op 00:00 van de volgende dag. Dus roep je het script op 11 maart om 15:00 aan, dan is <code>tonight</code> gelijk aan 12 maart, 00:00.</p><p>In plaats van <code>response = requests.get(url)</code> maak je de HTTP-request met <code>response = session.get(url)</code>. Die sessie maak je eerst aan, en je stelt in wanneer de cache moet verlopen. In dit geval stel je het in op <code>tonight</code> (00:00).</p><p>De rest van de functie hou je gelijk.</p><p>Roep je deze functie nu meerdere malen aan op een dag, dan zal het de eerste keer de data downloaden, maar bij elke volgende aanroep de opgeslagen response gebruiken. Roep je de functie een dag later weer aan (na 00:00), dan zal het de data de eerste keer wel weer downloaden.</p><p>Standaard gebruikt <code>requests-cache</code> een <a href="https://www.sqlite.org/">sqlite</a> database om de response in op te slaan. Lees <a href="https://requests-cache.readthedocs.io/en/stable/">de documentatie</a> om te leren hoe je andere opslagmethoden gebruikt en om meer te leren over <code>requests-cache</code>.</p>]]></content:encoded></item><item><title><![CDATA[Moet ik nog leren programmeren met de opkomst van AI?]]></title><description><![CDATA[Leer meer over de opkomst van generatieve AI en de invloed op programmeren. Is het nog zinvol om te leren programmeren? Ontdek het in dit artikel.]]></description><link>https://pythoncursus.substack.com/p/moet-ik-nog-leren-programmeren-met</link><guid isPermaLink="false">https://pythoncursus.substack.com/p/moet-ik-nog-leren-programmeren-met</guid><dc:creator><![CDATA[python-cursus.nl]]></dc:creator><pubDate>Thu, 14 Mar 2024 07:43:24 GMT</pubDate><enclosure url="https://substack-post-media.s3.amazonaws.com/public/images/47520a59-aee8-491b-a70c-d2b52ff96721_1024x1024.webp" length="0" type="image/jpeg"/><content:encoded><![CDATA[<p>De opkomst van generatieve AI, het meest bekend van ChatGPT, heeft voor veel opschudding gezorgd. E&#233;n van de dingen waar de Large Language Models (LLM) erg goed in zijn, is het genereren van code. ChatGPT kan dit goed, maar er zijn ook modellen die speciaal op code zijn getraind. Een veel gebruikt voorbeeld hiervan is CodeLLama. Door deze ontwikkelingen rijst bij jou misschien de vraag: moet ik nog wel leren programmeren? Heeft dat nog zin? Een terechte vraag! In dit artikel neem ik je graag mee in hoe ik hier tegenaan kijk.</p><h2>Wat is programmeren?</h2><p>Voordat we ingaan op de toekomst van programmeren in de wereld van AI, is het belangrijk vast te stellen wat programmeren eigenlijk is. Als je bijvoorbeeld kijkt naar de uitleg op <a href="https://nl.wikipedia.org/wiki/Programmeren_(computer)">Wikipedia</a> zie je dat de focus erg ligt op het schrijven van de code:</p><blockquote><p>Programmeren is het schrijven van een computerprogramma, een concrete reeks instructies die een computer kan uitvoeren. Dit is de taak van een softwareontwikkelaar of programmeur.</p></blockquote><p>Maar wat belangrijk is om te onthouden, is dat er heel wat werk nodig is rondom het schrijven van code. Bijvoorbeeld:</p><ul><li><p>Het probleem dat je wilt oplossen goed defini&#235;ren</p></li><li><p>Wensen en eisen van gebruikers ophalen</p></li><li><p>Deze wensen en eisen vertalen naar mogelijke oplossingen</p></li><li><p>Deze mogelijke oplossingen tegen elkaar afwegen</p></li><li><p>Prioriteren wat je eerst gaat doen</p></li><li><p>Afstemmen met collega's</p></li><li><p>Afstemmen met opdrachtgevers</p></li><li><p>Je tussentijdse resultaten demonstreren, feedback ophalen</p></li><li><p>...</p></li></ul><p>E&#233;n van de moeilijkere dingen binnen het programmeren is de wensen en eisen scherp krijgen. Stel dat je werkt aan een systeem waar gebruikers hun projecten kunnen beheren. De opdrachtgever komt met een wens: "Ik wil dat gebruikers een project kunnen kopi&#235;ren naar een nieuw project". Ok, een paar vragen:</p><ul><li><p>Wat moet er allemaal gekopieerd worden? Alle openstaande items, afgehandelde items, naam, deadlines, projectmedewerkers?</p></li><li><p>Moeten er voor het kopi&#235;ren wijzigingen gemaakt kunnen worden? Welke?</p></li><li><p>Hoe zien de stappen eruit? Vanuit waar wordt er gekopieerd? Hoe zien alle schermen eruit?</p></li><li><p>Kan iedereen een kopie maken? Of alleen beheerders? Van welke projecten wel, van welke niet?</p></li><li><p>En zo nog 100 vragen.</p></li></ul><p>Voordat je code kunt schrijven moeten al deze vragen helder zijn, anders weet je niet welke code je moet schrijven. Programmeren is uiteindelijk het vertalen van de <em>intentie</em> van de maker naar instructies die een computer kan gebruiken. Dit vertalen van intentie naar computercode vraagt een precieze en exacte taal. Oftewel: een programmeertaal.</p><h2>Wat kan AI wel en niet?</h2><p>De huidige generatie AI kan dit vertalen van intentie naar computercode niet. Je kunt wel de opdracht geven: "Schrijf code dat ervoor zorgt dat de gebruiker een bestaand project naar een nieuw project kan kopi&#235;ren". En de AI zal moeiteloos code genereren. Maar of het goed is, is maar de vraag. Afhankelijk van het model zal het best ver kunnen komen, zeker als het de context van de rest van je code kent. Het ziet dat het project in Python is geschreven, het ziet dat je met Django werkt en herkent de algemene structuur van een scherm. Maar dit is allemaal al veel gevraagd. En dan nog. Het stelt niet al die bovenstaande vragen: wie moet er kunnen kopi&#235;ren? Welke projecten wel, welke niet? Welke elementen wel, welke niet? Etc. Dit zou je dan allemaal in de opdracht moeten meegeven.</p><p>Wat AI wel heel goed kan is bijvoorbeeld jouw code lezen, en suggesties doen voor testen. Of jouw code lezen en suggesties doen voor verbeteringen. Ook kun je AI afgebakende instructies geven: schrijf een functie die deze parameters als input heeft en dit als resultaat geeft. Maar ook hier geldt: je moet precies zijn om de gewenste resultaten te krijgen. Toch kom je hiermee een heel eind. Sommige modellen kunnen ook heel goed suggesties doen in jouw code, op basis van de omliggende code. De AI begrijpt waar je aan werkt, en wat er nog nodig is.</p><p>Maar in alle gevallen geldt: jij als programmeur hebt dan al bedacht wat de wensen en eisen zijn en hoe die vertaald moeten worden naar code. Het code schrijven gaat met behulp van AI sneller en makkelijker, maar een belangrijk deel (het nadenken) doe je dus nog zelf.</p><p>Bovendien is AI (nog) niet foutloos. Een bekend fenomeen is het zogenaamde "hallucineren": de AI weet niet precies wat het moet doen, en verzint maar wat. Dit kan soms subtiel zijn. Gebruik je AI om code te schrijven, dan is het dus heel belangrijk dat je het altijd zelf nog controleert. En om dat te kunnen doen moet je wel weten hoe het in elkaar steekt, anders haal je de (subtiele) fouten er niet uit.</p><h2>Conclusie</h2><p>Kortom: AI is een geweldig hulpmiddel, maar momenteel niet meer dan dat. Het is een soort <em>autocomplete on steroids</em>. Het kan zeker het proces van het code schrijven versnellen, en je kunt er zelfs van leren. Bijvoorbeeld door te vragen: hoe kan ik mijn code beter maken?</p><p>Maar vooralsnog is AI geen complete vervanging van een programmeur, omdat een AI niet de intentie van een klant, gebruiker of opdrachtgever kan omzetten naar code. Wie weet kan dit in de toekomst wel, dan kan de AI niet alleen code genereren, maar ook heel goed uitvragen wat de precieze wensen zijn. Maar zover zijn we nog niet, en dus is het zeker nog zinvol om te leren programmeren. Al is het maar om de code die AI genereert te kunnen controleren.</p><p>Niemand kan in de toekomst kijken. Maar voorlopig durf ik de stelling wel aan dat het zeker nog zinvol is om te leren programmeren!</p>]]></content:encoded></item><item><title><![CDATA[Werken met JSON]]></title><description><![CDATA[Leer hoe je met JSON-data in Python werkt. Ontdek hoe je gegevens downloadt, opslaat en verwerkt met behulp van Python's ingebouwde 'json'-bibliotheek.]]></description><link>https://pythoncursus.substack.com/p/werken-met-json</link><guid isPermaLink="false">https://pythoncursus.substack.com/p/werken-met-json</guid><dc:creator><![CDATA[python-cursus.nl]]></dc:creator><pubDate>Thu, 22 Feb 2024 13:40:51 GMT</pubDate><enclosure url="https://substack-post-media.s3.amazonaws.com/public/images/76d2ab5c-93d7-447d-b46d-4683337794eb_1024x1024.webp" length="0" type="image/jpeg"/><content:encoded><![CDATA[<p>In <a href="https://python-cursus.nl/blog/geautomatiseerd-gegevens-downloaden/">Geautomatiseerd gegevens downloaden</a> heb je geleerd hoe je met <code>requests</code> data kunt ophalen van een URL. In <a href="https://python-cursus.nl/blog/website-scrapen/">Een website scrapen met Beautiful Soup</a> leerde je hoe je gegevens van een website zelf kunt verwerken met behulp van <a href="https://beautiful-soup-4.readthedocs.io/en/latest/#">Beautiful Soup</a>. In het eerste geval kwam de data als tekstbestand, in het tweede geval als HTML. Maar wat als de data die je wilt ophalen in <a href="https://nl.wikipedia.org/wiki/JSON">JSON-formaat</a> is? In deze tutorial leer je hoe je met JSON-data kunt werken in Python.</p><h2>Het project</h2><p>Het Centraal Bureau voor de Statistiek (CBS) maakt alle gegevens niet alleen via de website beschikbaar, maar ook via een API (<a href="https://opendata.cbs.nl/statline/portal.html?_la=nl&amp;_catalog=CBS">https://opendata.cbs.nl/statline/portal.html</a>). In dit project download je de bevolkingsgegevens, sla je ze op in <code>data.json</code> en verwerk je ze vervolgens om per jaar het totale bevolkingsaantal van Nederland te tonen.</p><p>output</p><pre><code><code>1950: 10.026.773
1951: 10.200.280
...
2022: 17.590.672
2023: 17.811.291
</code></code></pre><h2>Voordat je begint</h2><p>Maak een nieuw project aan, met een nieuwe <a href="https://python-cursus.nl/blog/pip-en-venv/">virtual environment</a>. Installeer daarin de benodigde <em>packages</em>:</p><p>shell</p><pre><code><code>cd projects
mkdir cbs_json &amp;&amp; cd cbs_json
python -m venv .venv
source .venv/bin/activate  # .venv\Scripts\activate.bat voor Windows
python -m pip install requests
python -m pip freeze &gt; requirements.txt
</code></code></pre><h2>Wat is JSON?</h2><p>JSON staat voor JavaScript Object Notation. Het is een lichtgewicht dataformaat dat makkelijk voor mensen te lezen en schrijven is en makkelijk voor machines om te genereren en analyseren. Het wordt veel gebruikt om gegevens tussen programma's uit te wisselen, met name via internet. Veel API's zullen dan ook JSON teruggeven.</p><p>In onderstaand voorbeeld van de <a href="https://nl.wikipedia.org/wiki/JSON">Wikipediapagina over JSON</a>, zie je een lijst met twee elementen:</p><p>json</p><pre><code><code>[
   {
      "Naam":"JSON",
      "Type":"Gegevensuitwisselingsformaat",
      "isProgrammeertaal":false,
      "Zie ook":[
         "XML",
         "ASN.1"
      ]
   },
   {
      "Naam":"JavaScript",
      "Type":"Programmeertaal",
      "isProgrammeertaal":true,
      "Jaar":1995
   }
]
</code></code></pre><p>Zoals je ziet, lijkt een JSON-object erg op een <code>dict</code> in Python. Net als een <code>dict</code> wordt een JSON-object omsloten door <em>curly braces</em> (<code>{}</code>) en bestaat het uit sleutel-waarden paren. Er zijn wel een aantal beperkingen. Als waarde zijn alleen de volgende datatypes toegestaan:</p><ul><li><p>Tekst</p></li><li><p>Getallen</p></li><li><p><code>true</code>, <code>false</code> en <code>null</code></p></li><li><p>Lijsten (<em>arrays</em>, <code>[]</code>)</p></li><li><p>Andere JSON-objecten (<code>{}</code>)</p></li></ul><p>De sleutels zijn altijd tekst.</p><h2>JSON in Python</h2><p>Python heeft een ingebouwde bibliotheek genaamd <code>json</code> voor het werken met JSON-data. Er zijn twee manieren om dit te doen: van en naar <em>strings</em> en van en naar bestanden:</p><ul><li><p>Van Python naar een JSON-string: <code>json.dumps()</code></p></li><li><p>Van Python naar een JSON-bestand: <code>json.dump()</code></p></li><li><p>Van een JSON-string naar Python: <code>json.loads()</code></p></li><li><p>Van een JSON-bestand naar Python: <code>json.load()</code></p></li></ul><p>Van Python naar JSON noem je <strong>serialiseren</strong>. Van JSON naar Python noem je <strong>deserialiseren</strong>.</p><h2>Verder met het project</h2><p>Nu je de basis van JSON begrijpt, kun je verder met het project.</p><h3>Gegevens downloaden</h3><p>Stap 1 is het downloaden van de gegevens. Doe dit met <code>requests</code>. Maak in <code>main.py</code> een functie aan om de gegevens te downloaden. Deze functie is vrijwel gelijk aan de <code>get_html</code> functie uit <a href="https://python-cursus.nl/blog/website-scrapen/">Een website scrapen met Beautiful Soup</a>.</p><p>main.py</p><pre><code><code>import json
import requests

URL = "https://opendata.cbs.nl/ODataApi/odata/85496NED/TypedDataSet"

def get_data(refresh=True):
    if not refresh:
        try:
            with open("data.json") as f:
                data = json.load(f)
        except FileNotFoundError:
            print("Bestand niet gevonden, downloaden")

            # Roep deze functie aan, met `refresh=True`
            return get_data(refresh=True)
    else:
        response = requests.get(URL)

        # Als de HTTP-code 400 of hoger is, zal er een uitzondering worden opgeworpen
        response.raise_for_status()
        data = response.json()

        # Sla bestand op
        with open("data.json", "w") as f:
            json.dump(data, f, indent=2)

    return data
</code></code></pre><p>De functie zorgt ervoor dat je het bestand opslaat als <code>data.json</code>, zodat je tijdens het ontwikkelen niet steeds de URL hoeft aan te roepen. Wil je de gegevens verversen, roep dan <code>get_data</code> aan met <code>refresh=True</code>.</p><p>De gearceerde regels laten zien je waar je in de code met <code>json</code> werkt. Als je <code>data.json</code> wilt inladen, gebruik je <code>json.load()</code>. Haal je de gegevens eerst op van het CBS met <code>requests</code>, dan gebruik je <code>data = response.json()</code>. Dit is een hulpmiddel van <code>requests</code> om inkomende <code>json</code> om te zetten naar een Python-object. Sla je het bestand vervolgens op, dan gebruik je <code>json.dump(data, f, indent=2)</code>. Dus: je serialiseert het Python-object <code>data</code> naar bestand <code>f</code>. Om het leesbaar te maken gebruik je een indentatie van 2.</p><p>Roep de functie eens aan en inspecteer het opgeslagen bestand:</p><p>main.py</p><pre><code><code># ...

if __name__ == "__main__":
    data = get_data()
</code></code></pre><p>Het resultaat ziet er ongeveer zo uit:</p><div class="captioned-image-container"><figure><a class="image-link image2 is-viewable-img" target="_blank" href="https://substackcdn.com/image/fetch/$s_!pV4e!,f_auto,q_auto:good,fl_progressive:steep/https%3A%2F%2Fsubstack-post-media.s3.amazonaws.com%2Fpublic%2Fimages%2Fdd7bd583-9194-48fd-9df5-af4cf066069a_1024x334.webp" data-component-name="Image2ToDOM"><div class="image2-inset"><picture><source type="image/webp" srcset="https://substackcdn.com/image/fetch/$s_!pV4e!,w_424,c_limit,f_webp,q_auto:good,fl_progressive:steep/https%3A%2F%2Fsubstack-post-media.s3.amazonaws.com%2Fpublic%2Fimages%2Fdd7bd583-9194-48fd-9df5-af4cf066069a_1024x334.webp 424w, https://substackcdn.com/image/fetch/$s_!pV4e!,w_848,c_limit,f_webp,q_auto:good,fl_progressive:steep/https%3A%2F%2Fsubstack-post-media.s3.amazonaws.com%2Fpublic%2Fimages%2Fdd7bd583-9194-48fd-9df5-af4cf066069a_1024x334.webp 848w, https://substackcdn.com/image/fetch/$s_!pV4e!,w_1272,c_limit,f_webp,q_auto:good,fl_progressive:steep/https%3A%2F%2Fsubstack-post-media.s3.amazonaws.com%2Fpublic%2Fimages%2Fdd7bd583-9194-48fd-9df5-af4cf066069a_1024x334.webp 1272w, https://substackcdn.com/image/fetch/$s_!pV4e!,w_1456,c_limit,f_webp,q_auto:good,fl_progressive:steep/https%3A%2F%2Fsubstack-post-media.s3.amazonaws.com%2Fpublic%2Fimages%2Fdd7bd583-9194-48fd-9df5-af4cf066069a_1024x334.webp 1456w" sizes="100vw"><img src="https://substackcdn.com/image/fetch/$s_!pV4e!,w_1456,c_limit,f_auto,q_auto:good,fl_progressive:steep/https%3A%2F%2Fsubstack-post-media.s3.amazonaws.com%2Fpublic%2Fimages%2Fdd7bd583-9194-48fd-9df5-af4cf066069a_1024x334.webp" width="1024" height="334" data-attrs="{&quot;src&quot;:&quot;https://substack-post-media.s3.amazonaws.com/public/images/dd7bd583-9194-48fd-9df5-af4cf066069a_1024x334.webp&quot;,&quot;srcNoWatermark&quot;:null,&quot;fullscreen&quot;:null,&quot;imageSize&quot;:null,&quot;height&quot;:334,&quot;width&quot;:1024,&quot;resizeWidth&quot;:null,&quot;bytes&quot;:null,&quot;alt&quot;:&quot;Een deel van het JSON-bestand van het CBS&quot;,&quot;title&quot;:null,&quot;type&quot;:null,&quot;href&quot;:null,&quot;belowTheFold&quot;:true,&quot;topImage&quot;:false,&quot;internalRedirect&quot;:null,&quot;isProcessing&quot;:false,&quot;align&quot;:null,&quot;offset&quot;:false}" class="sizing-normal" alt="Een deel van het JSON-bestand van het CBS" title="Een deel van het JSON-bestand van het CBS" srcset="https://substackcdn.com/image/fetch/$s_!pV4e!,w_424,c_limit,f_auto,q_auto:good,fl_progressive:steep/https%3A%2F%2Fsubstack-post-media.s3.amazonaws.com%2Fpublic%2Fimages%2Fdd7bd583-9194-48fd-9df5-af4cf066069a_1024x334.webp 424w, https://substackcdn.com/image/fetch/$s_!pV4e!,w_848,c_limit,f_auto,q_auto:good,fl_progressive:steep/https%3A%2F%2Fsubstack-post-media.s3.amazonaws.com%2Fpublic%2Fimages%2Fdd7bd583-9194-48fd-9df5-af4cf066069a_1024x334.webp 848w, https://substackcdn.com/image/fetch/$s_!pV4e!,w_1272,c_limit,f_auto,q_auto:good,fl_progressive:steep/https%3A%2F%2Fsubstack-post-media.s3.amazonaws.com%2Fpublic%2Fimages%2Fdd7bd583-9194-48fd-9df5-af4cf066069a_1024x334.webp 1272w, https://substackcdn.com/image/fetch/$s_!pV4e!,w_1456,c_limit,f_auto,q_auto:good,fl_progressive:steep/https%3A%2F%2Fsubstack-post-media.s3.amazonaws.com%2Fpublic%2Fimages%2Fdd7bd583-9194-48fd-9df5-af4cf066069a_1024x334.webp 1456w" sizes="100vw" loading="lazy"></picture><div class="image-link-expand"><div class="pencraft pc-display-flex pc-gap-8 pc-reset"><button tabindex="0" type="button" class="pencraft pc-reset pencraft icon-container restack-image"><svg aria-hidden="true" width="20" height="20" viewBox="0 0 20 20" fill="none" stroke-width="1.5" stroke="var(--color-fg-primary)" stroke-linecap="round" stroke-linejoin="round" xmlns="http://www.w3.org/2000/svg"><g><path d="M2.53001 7.81595C3.49179 4.73911 6.43281 2.5 9.91173 2.5C13.1684 2.5 15.9537 4.46214 17.0852 7.23684L17.6179 8.67647M17.6179 8.67647L18.5002 4.26471M17.6179 8.67647L13.6473 6.91176M17.4995 12.1841C16.5378 15.2609 13.5967 17.5 10.1178 17.5C6.86118 17.5 4.07589 15.5379 2.94432 12.7632L2.41165 11.3235M2.41165 11.3235L1.5293 15.7353M2.41165 11.3235L6.38224 13.0882"></path></g></svg></button><button tabindex="0" type="button" class="pencraft pc-reset pencraft icon-container view-image"><svg xmlns="http://www.w3.org/2000/svg" width="20" height="20" viewBox="0 0 24 24" fill="none" stroke="currentColor" stroke-width="2" stroke-linecap="round" stroke-linejoin="round" class="lucide lucide-maximize2 lucide-maximize-2"><polyline points="15 3 21 3 21 9"></polyline><polyline points="9 21 3 21 3 15"></polyline><line x1="21" x2="14" y1="3" y2="10"></line><line x1="3" x2="10" y1="21" y2="14"></line></svg></button></div></div></div></a></figure></div><p>Alle gegevens zijn te vinden in de sleutel <code>value</code>, welke een lijst bevat met JSON-objecten. Elk jaar is &#233;&#233;n object. Voor dit project heb je uit die objecten alleen de sleutels <code>Perioden</code> en <code>TotaleBevolking_1</code> nodig.</p><h3>Gegevens verwerken</h3><p>Nu je de gegevens in <code>data.json</code> beschikbaar hebt, kun je datgene eruit halen wat je nodig hebt. Maak een functie <code>get_total_population</code> aan in <code>main.py</code>.</p><p>main.py</p><pre><code><code># ...

def get_total_population():

    # Open het bestand
    with open("data.json") as f:
        data = json.load(f)  # data is nu een Python object (dict)

    # Bereid een lege lijst voor
    result = []

    # Loop door de rijen in de data
    for item in data["value"]:
        year = item["Perioden"].split("JJ")[0]  # Schoon het jaartal op
        value = item["TotaleBevolking_1"]

        # Voeg het jaartal en de waarde toe aan de lijst als een tuple
        result.append((year, value))

    return result
</code></code></pre><p>Je begint met het inladen van het JSON-bestand, wat zal resulteren in een <code>dict</code>. Vervolgens <em>loop</em> je over alle items in de <code>data["value"]</code> lijst om er het jaar (<code>Perioden</code>) en de bevolkingsomvang (<code>TotaleBevolking_1</code>) uit te halen. Schoon het jaar op, zodat je in plaats van "1950JJ00" netjes "1950" krijgt.</p><p>Roep het geheel tot slot aan en druk de resultaten af:</p><p>main.py</p><pre><code><code>import locale
locale.setlocale(locale.LC_ALL, "nl_NL.utf8")
# ...

if __name__ == "__main__":
    data = get_data(refresh=False)  # Zet op False om niet steeds te downloaden
    total_population = get_total_population()

    for year, value in total_population:
        print(f"{year}: {value:n}")
</code></code></pre><p>Bovenaan je bestand stel je <code>locale</code> in, zodat je op de laatste regel <code>{value:n}</code> kunt gebruiken om duizendtallen netjes te scheiden door een punt.</p><p>Het resultaat:</p><div class="captioned-image-container"><figure><a class="image-link image2 is-viewable-img" target="_blank" href="https://substackcdn.com/image/fetch/$s_!9rPT!,f_auto,q_auto:good,fl_progressive:steep/https%3A%2F%2Fsubstack-post-media.s3.amazonaws.com%2Fpublic%2Fimages%2Fee6d7f48-9c95-4372-990c-9abe4c0e09dd_329x293.webp" data-component-name="Image2ToDOM"><div class="image2-inset"><picture><source type="image/webp" srcset="https://substackcdn.com/image/fetch/$s_!9rPT!,w_424,c_limit,f_webp,q_auto:good,fl_progressive:steep/https%3A%2F%2Fsubstack-post-media.s3.amazonaws.com%2Fpublic%2Fimages%2Fee6d7f48-9c95-4372-990c-9abe4c0e09dd_329x293.webp 424w, https://substackcdn.com/image/fetch/$s_!9rPT!,w_848,c_limit,f_webp,q_auto:good,fl_progressive:steep/https%3A%2F%2Fsubstack-post-media.s3.amazonaws.com%2Fpublic%2Fimages%2Fee6d7f48-9c95-4372-990c-9abe4c0e09dd_329x293.webp 848w, https://substackcdn.com/image/fetch/$s_!9rPT!,w_1272,c_limit,f_webp,q_auto:good,fl_progressive:steep/https%3A%2F%2Fsubstack-post-media.s3.amazonaws.com%2Fpublic%2Fimages%2Fee6d7f48-9c95-4372-990c-9abe4c0e09dd_329x293.webp 1272w, https://substackcdn.com/image/fetch/$s_!9rPT!,w_1456,c_limit,f_webp,q_auto:good,fl_progressive:steep/https%3A%2F%2Fsubstack-post-media.s3.amazonaws.com%2Fpublic%2Fimages%2Fee6d7f48-9c95-4372-990c-9abe4c0e09dd_329x293.webp 1456w" sizes="100vw"><img src="https://substackcdn.com/image/fetch/$s_!9rPT!,w_1456,c_limit,f_auto,q_auto:good,fl_progressive:steep/https%3A%2F%2Fsubstack-post-media.s3.amazonaws.com%2Fpublic%2Fimages%2Fee6d7f48-9c95-4372-990c-9abe4c0e09dd_329x293.webp" width="329" height="293" data-attrs="{&quot;src&quot;:&quot;https://substack-post-media.s3.amazonaws.com/public/images/ee6d7f48-9c95-4372-990c-9abe4c0e09dd_329x293.webp&quot;,&quot;srcNoWatermark&quot;:null,&quot;fullscreen&quot;:null,&quot;imageSize&quot;:null,&quot;height&quot;:293,&quot;width&quot;:329,&quot;resizeWidth&quot;:null,&quot;bytes&quot;:null,&quot;alt&quot;:&quot;Een deel van het resultaat&quot;,&quot;title&quot;:null,&quot;type&quot;:null,&quot;href&quot;:null,&quot;belowTheFold&quot;:true,&quot;topImage&quot;:false,&quot;internalRedirect&quot;:null,&quot;isProcessing&quot;:false,&quot;align&quot;:null,&quot;offset&quot;:false}" class="sizing-normal" alt="Een deel van het resultaat" title="Een deel van het resultaat" srcset="https://substackcdn.com/image/fetch/$s_!9rPT!,w_424,c_limit,f_auto,q_auto:good,fl_progressive:steep/https%3A%2F%2Fsubstack-post-media.s3.amazonaws.com%2Fpublic%2Fimages%2Fee6d7f48-9c95-4372-990c-9abe4c0e09dd_329x293.webp 424w, https://substackcdn.com/image/fetch/$s_!9rPT!,w_848,c_limit,f_auto,q_auto:good,fl_progressive:steep/https%3A%2F%2Fsubstack-post-media.s3.amazonaws.com%2Fpublic%2Fimages%2Fee6d7f48-9c95-4372-990c-9abe4c0e09dd_329x293.webp 848w, https://substackcdn.com/image/fetch/$s_!9rPT!,w_1272,c_limit,f_auto,q_auto:good,fl_progressive:steep/https%3A%2F%2Fsubstack-post-media.s3.amazonaws.com%2Fpublic%2Fimages%2Fee6d7f48-9c95-4372-990c-9abe4c0e09dd_329x293.webp 1272w, https://substackcdn.com/image/fetch/$s_!9rPT!,w_1456,c_limit,f_auto,q_auto:good,fl_progressive:steep/https%3A%2F%2Fsubstack-post-media.s3.amazonaws.com%2Fpublic%2Fimages%2Fee6d7f48-9c95-4372-990c-9abe4c0e09dd_329x293.webp 1456w" sizes="100vw" loading="lazy"></picture><div class="image-link-expand"><div class="pencraft pc-display-flex pc-gap-8 pc-reset"><button tabindex="0" type="button" class="pencraft pc-reset pencraft icon-container restack-image"><svg aria-hidden="true" width="20" height="20" viewBox="0 0 20 20" fill="none" stroke-width="1.5" stroke="var(--color-fg-primary)" stroke-linecap="round" stroke-linejoin="round" xmlns="http://www.w3.org/2000/svg"><g><path d="M2.53001 7.81595C3.49179 4.73911 6.43281 2.5 9.91173 2.5C13.1684 2.5 15.9537 4.46214 17.0852 7.23684L17.6179 8.67647M17.6179 8.67647L18.5002 4.26471M17.6179 8.67647L13.6473 6.91176M17.4995 12.1841C16.5378 15.2609 13.5967 17.5 10.1178 17.5C6.86118 17.5 4.07589 15.5379 2.94432 12.7632L2.41165 11.3235M2.41165 11.3235L1.5293 15.7353M2.41165 11.3235L6.38224 13.0882"></path></g></svg></button><button tabindex="0" type="button" class="pencraft pc-reset pencraft icon-container view-image"><svg xmlns="http://www.w3.org/2000/svg" width="20" height="20" viewBox="0 0 24 24" fill="none" stroke="currentColor" stroke-width="2" stroke-linecap="round" stroke-linejoin="round" class="lucide lucide-maximize2 lucide-maximize-2"><polyline points="15 3 21 3 21 9"></polyline><polyline points="9 21 3 21 3 15"></polyline><line x1="21" x2="14" y1="3" y2="10"></line><line x1="3" x2="10" y1="21" y2="14"></line></svg></button></div></div></div></a></figure></div><h2>Conclusie</h2><p>Werken met JSON in Python is eenvoudig te doen met de <code>json</code> <em>package</em>, beschikbaar in de standaard bibliotheek. Het omzetten van Python-objecten naar JSON doe je met <code>dump</code> of <code>dumps</code>. Het omzetten van JSON naar Python doe je met <code>load</code> of <code>loads</code>. Vaak zul je JSON ophalen via een API. Gebruik je hiervoor <code>requests</code>, dan kun je <code>response.json()</code> gebruiken om de inkomende data te converteren naar een Python-object.</p>]]></content:encoded></item><item><title><![CDATA[Geautomatiseerd gegevens downloaden]]></title><description><![CDATA[Als je vaak dezelfde gegevens download om mee te werken, bijvoorbeeld van een API, is het handig om dit te automatiseren.]]></description><link>https://pythoncursus.substack.com/p/geautomatiseerd-gegevens-downloaden</link><guid isPermaLink="false">https://pythoncursus.substack.com/p/geautomatiseerd-gegevens-downloaden</guid><dc:creator><![CDATA[python-cursus.nl]]></dc:creator><pubDate>Mon, 12 Feb 2024 12:52:57 GMT</pubDate><enclosure url="https://substack-post-media.s3.amazonaws.com/public/images/928af380-9177-4e79-b170-5e5f818faf5c_1024x1024.png" length="0" type="image/jpeg"/><content:encoded><![CDATA[<p>Als je vaak dezelfde gegevens download om mee te werken, bijvoorbeeld van een <a href="https://nl.wikipedia.org/wiki/Application_programming_interface">API</a>, is het handig om dit te automatiseren. In deze tutorial leer je hoe je <a href="https://requests.readthedocs.io/en/latest/">requests</a> gebruikt om gegevens te downloaden, ze in een CSV op te slaan en ze vervolgens opschoont.</p><h2>Het project</h2><p>In dit project download je data van het KNMI. Dit komt in een <code>txt</code>-bestand. Nadat je het tekstbestand hebt opgeslagen, schoon je het op en sla je het op als CSV-bestand, zodat er eenvoudiger mee is te werken. De data zijn maand- en jaargemiddelde temperaturen van het meetstation De Bilt. De url is:</p><blockquote><p>https://cdn.knmi.nl/knmi/map/page/klimatologie/gegevens/maandgegevens/mndgeg_260_tg.txt</p></blockquote><h2>Voordat je begint</h2><p>Lees voordat je begint nog eens <a href="https://python-cursus.nl/blog/pip-en-venv/">Werken met pip en venv</a> door als een opfrisser nodig hebt over hoe je een <em>virtual environment</em> aanmaakt en daarin <em>packages</em> installeert.</p><p>Maak vervolgens een nieuw project aan en installeer <code>requests</code>:</p><p>shell</p><pre><code><code>cd projects
mkdir knmi &amp;&amp; cd knmi
python -m venv .venv
source .venv/bin/activate  # .venv\Scripts\activate.bat voor Windows
python -m pip install requests
python -m pip freeze &gt; requirements.txt
</code></code></pre><h2>Data downloaden met requests</h2><p><code>requests</code> is een <em>package</em> dat het werken met urls eenvoudig maakt. In hun eigen woorden:</p><blockquote><p>Requests is an elegant and simple HTTP library for Python, built for human beings.</p></blockquote><p>Maak een <code>main.py</code> aan maak daarin een eenvoudige <code>download</code>-functie.</p><p>main.py</p><pre><code><code>import requests

URL = "https://cdn.knmi.nl/knmi/map/page/klimatologie/gegevens/maandgegevens/mndgeg_260_tg.txt"
TXT = "knmi.txt"

def download():
    response = requests.get(URL)

    # Als de HTTP-code 400 of hoger is, zal er een uitzondering worden opgeworpen
    response.raise_for_status()  

    # Sla het bestand op
    with open(TXT, "w", encoding="utf-8") as file:
        file.write(response.text)
</code></code></pre><p>Je ziet hoe eenvoudig <code>requests</code> werkt. Met <code>response.raise_for_status()</code> controleer je of de HTTP-code lager is dan 400. Zo niet, dan <a href="https://requests.readthedocs.io/en/latest/user/quickstart/#errors-and-exceptions">wordt er een uitzondering opgeworpen</a>. Alles in de 4XX en 5XX reeks betekent namelijk dat er iets niet goed ging. Zie <a href="https://developer.mozilla.org/en-US/docs/Web/HTTP/Status">de lijst met HTTP-codes</a> voor meer informatie.</p><p>Je slaat het bestand op de huidige locatie op (dus naast <code>main.py</code>). Met <code>"w"</code> geef je aan dat je het bestand wilt schrijven. Let op: als het bestand al bestaat, zal het hierdoor worden overschreven!</p><p>Voeg tot slot onderaan in <code>main.py</code> het volgende toe:</p><p>main.py</p><pre><code><code>if __name__ == "__main__":
  download()
</code></code></pre><p> Je kunt het bestand nu uitvoeren in je shell met <code>python main.py</code>. Als het goed is, is er nu een bestand <code>knmi.txt</code> opgeslagen. Open het bestand met een teksteditor naar keuze om te inspecteren.</p><div class="captioned-image-container"><figure><a class="image-link image2 is-viewable-img" target="_blank" href="https://substackcdn.com/image/fetch/$s_!wXvu!,f_auto,q_auto:good,fl_progressive:steep/https%3A%2F%2Fsubstack-post-media.s3.amazonaws.com%2Fpublic%2Fimages%2Faad75a36-4971-4960-b30a-12e69a7995c7_1067x538.png" data-component-name="Image2ToDOM"><div class="image2-inset"><picture><source type="image/webp" srcset="https://substackcdn.com/image/fetch/$s_!wXvu!,w_424,c_limit,f_webp,q_auto:good,fl_progressive:steep/https%3A%2F%2Fsubstack-post-media.s3.amazonaws.com%2Fpublic%2Fimages%2Faad75a36-4971-4960-b30a-12e69a7995c7_1067x538.png 424w, https://substackcdn.com/image/fetch/$s_!wXvu!,w_848,c_limit,f_webp,q_auto:good,fl_progressive:steep/https%3A%2F%2Fsubstack-post-media.s3.amazonaws.com%2Fpublic%2Fimages%2Faad75a36-4971-4960-b30a-12e69a7995c7_1067x538.png 848w, https://substackcdn.com/image/fetch/$s_!wXvu!,w_1272,c_limit,f_webp,q_auto:good,fl_progressive:steep/https%3A%2F%2Fsubstack-post-media.s3.amazonaws.com%2Fpublic%2Fimages%2Faad75a36-4971-4960-b30a-12e69a7995c7_1067x538.png 1272w, https://substackcdn.com/image/fetch/$s_!wXvu!,w_1456,c_limit,f_webp,q_auto:good,fl_progressive:steep/https%3A%2F%2Fsubstack-post-media.s3.amazonaws.com%2Fpublic%2Fimages%2Faad75a36-4971-4960-b30a-12e69a7995c7_1067x538.png 1456w" sizes="100vw"><img src="https://substackcdn.com/image/fetch/$s_!wXvu!,w_1456,c_limit,f_auto,q_auto:good,fl_progressive:steep/https%3A%2F%2Fsubstack-post-media.s3.amazonaws.com%2Fpublic%2Fimages%2Faad75a36-4971-4960-b30a-12e69a7995c7_1067x538.png" width="1067" height="538" data-attrs="{&quot;src&quot;:&quot;https://substack-post-media.s3.amazonaws.com/public/images/aad75a36-4971-4960-b30a-12e69a7995c7_1067x538.png&quot;,&quot;srcNoWatermark&quot;:null,&quot;fullscreen&quot;:null,&quot;imageSize&quot;:null,&quot;height&quot;:538,&quot;width&quot;:1067,&quot;resizeWidth&quot;:null,&quot;bytes&quot;:null,&quot;alt&quot;:&quot;De ruwe KNMI data&quot;,&quot;title&quot;:null,&quot;type&quot;:null,&quot;href&quot;:null,&quot;belowTheFold&quot;:true,&quot;topImage&quot;:false,&quot;internalRedirect&quot;:null,&quot;isProcessing&quot;:false,&quot;align&quot;:null,&quot;offset&quot;:false}" class="sizing-normal" alt="De ruwe KNMI data" title="De ruwe KNMI data" srcset="https://substackcdn.com/image/fetch/$s_!wXvu!,w_424,c_limit,f_auto,q_auto:good,fl_progressive:steep/https%3A%2F%2Fsubstack-post-media.s3.amazonaws.com%2Fpublic%2Fimages%2Faad75a36-4971-4960-b30a-12e69a7995c7_1067x538.png 424w, https://substackcdn.com/image/fetch/$s_!wXvu!,w_848,c_limit,f_auto,q_auto:good,fl_progressive:steep/https%3A%2F%2Fsubstack-post-media.s3.amazonaws.com%2Fpublic%2Fimages%2Faad75a36-4971-4960-b30a-12e69a7995c7_1067x538.png 848w, https://substackcdn.com/image/fetch/$s_!wXvu!,w_1272,c_limit,f_auto,q_auto:good,fl_progressive:steep/https%3A%2F%2Fsubstack-post-media.s3.amazonaws.com%2Fpublic%2Fimages%2Faad75a36-4971-4960-b30a-12e69a7995c7_1067x538.png 1272w, https://substackcdn.com/image/fetch/$s_!wXvu!,w_1456,c_limit,f_auto,q_auto:good,fl_progressive:steep/https%3A%2F%2Fsubstack-post-media.s3.amazonaws.com%2Fpublic%2Fimages%2Faad75a36-4971-4960-b30a-12e69a7995c7_1067x538.png 1456w" sizes="100vw" loading="lazy"></picture><div class="image-link-expand"><div class="pencraft pc-display-flex pc-gap-8 pc-reset"><button tabindex="0" type="button" class="pencraft pc-reset pencraft icon-container restack-image"><svg aria-hidden="true" width="20" height="20" viewBox="0 0 20 20" fill="none" stroke-width="1.5" stroke="var(--color-fg-primary)" stroke-linecap="round" stroke-linejoin="round" xmlns="http://www.w3.org/2000/svg"><g><path d="M2.53001 7.81595C3.49179 4.73911 6.43281 2.5 9.91173 2.5C13.1684 2.5 15.9537 4.46214 17.0852 7.23684L17.6179 8.67647M17.6179 8.67647L18.5002 4.26471M17.6179 8.67647L13.6473 6.91176M17.4995 12.1841C16.5378 15.2609 13.5967 17.5 10.1178 17.5C6.86118 17.5 4.07589 15.5379 2.94432 12.7632L2.41165 11.3235M2.41165 11.3235L1.5293 15.7353M2.41165 11.3235L6.38224 13.0882"></path></g></svg></button><button tabindex="0" type="button" class="pencraft pc-reset pencraft icon-container view-image"><svg xmlns="http://www.w3.org/2000/svg" width="20" height="20" viewBox="0 0 24 24" fill="none" stroke="currentColor" stroke-width="2" stroke-linecap="round" stroke-linejoin="round" class="lucide lucide-maximize2 lucide-maximize-2"><polyline points="15 3 21 3 21 9"></polyline><polyline points="9 21 3 21 3 15"></polyline><line x1="21" x2="14" y1="3" y2="10"></line><line x1="3" x2="10" y1="21" y2="14"></line></svg></button></div></div></div></a></figure></div><p>Je ziet dat het tekstbestand de koppen (eerste gele markering) en gegevens (tweede gele markering) bevat, maar begint met een toelichtende tekst. Als je geautomatiseerd wilt werken met de gegevens, heb je deze tekst niet nodig. Tijd voor de volgende stap!</p><h2>Het bestand opschonen</h2><p>Er zijn drie problemen met het tekstbestand.</p><p>Ten eerste de al eerder genoemde toelichtende tekst, die heb je niet nodig. Ten tweede is er een lege regel (niet heel erg, maar nu je toch bezig bent...). En tot slot is het bestand netjes opgemaakt in kolommen, waardoor er veel spaties zijn toegevoegd. Zou je het zo omzetten naar CSV, dan zouden die spaties onderdeel worden van de gegevens.</p><p>Uiteindelijk wil je een regel alleen behouden als het de koppen bevat &#243;f als het de gegevens bevat. De gegevens wil je opschonen door de spaties te verwijderen. Maak een functie <code>clean_data</code> aan in <code>main.py</code>:</p><p>main.py</p><pre><code><code># ...

TXT = "knmi.txt"
CSV = "knmi.csv"

# ...

def clean_data():
    # Open het tekstbestand
    with open(TXT, "r", encoding="utf-8") as file:
        lines = []
        for line in file:
            # Behoud alleen de koppen en de rijen met gegevens
            if line.startswith("STN,YYYY") or line.startswith("260"):
                # Verwijder spaties
                lines.append(line.replace(" ", ""))

    # En sla op als CSV-bestand
    with open(CSV, "w", encoding="utf-8") as file:
        file.writelines(lines)

# ....
</code></code></pre><p>Met deze functie behoud je alleen de regels die relevant zijn en schrijf je ze naar een CSV-bestand. Na het openen van het tekstbestand maak je een lege <code>list</code> aan, waarin je de gewenste regels gaat toevoegen.</p><p>Een regel wil je alleen behouden als het de koppen bevat (begint met "STN,YYYY") of als het de gegevens bevat (begint met "260"). Als dit zo is, voeg je de regel toe aan <code>lines</code>, nadat je de spaties hebt verwijderd met <code>line.replace(" ", """)</code>.</p><p>STN,YYYY?</p><p>Kun je ook bedenken waarom we testen op "STN,YYYY" om de regel met koppen te bewaren, en niet simpelweg op "STN"?</p><p>Tot slot maak je een nieuw CSV-bestand aan in schrijf-modus. Je schrijft met <code>file.writelines(lines)</code> alle regels naar het bestand, en klaar!</p><p>Voeg <code>clean_data</code> toe aan het <code>if __name__ == __main__</code> blok.</p><p>main.py</p><pre><code><code># ...

if __name__ == "__main__":
    download()
    clean_data()
</code></code></pre><p>Voer het weer uit vanuit je shell met <code>python main.py</code>. Open het bestand in een teksteditor om te bekijken.</p><div class="captioned-image-container"><figure><a class="image-link image2 is-viewable-img" target="_blank" href="https://substackcdn.com/image/fetch/$s_!jOI9!,f_auto,q_auto:good,fl_progressive:steep/https%3A%2F%2Fsubstack-post-media.s3.amazonaws.com%2Fpublic%2Fimages%2Fcc0637e4-145a-4b69-a936-7d57e89be14c_664x350.png" data-component-name="Image2ToDOM"><div class="image2-inset"><picture><source type="image/webp" srcset="https://substackcdn.com/image/fetch/$s_!jOI9!,w_424,c_limit,f_webp,q_auto:good,fl_progressive:steep/https%3A%2F%2Fsubstack-post-media.s3.amazonaws.com%2Fpublic%2Fimages%2Fcc0637e4-145a-4b69-a936-7d57e89be14c_664x350.png 424w, https://substackcdn.com/image/fetch/$s_!jOI9!,w_848,c_limit,f_webp,q_auto:good,fl_progressive:steep/https%3A%2F%2Fsubstack-post-media.s3.amazonaws.com%2Fpublic%2Fimages%2Fcc0637e4-145a-4b69-a936-7d57e89be14c_664x350.png 848w, https://substackcdn.com/image/fetch/$s_!jOI9!,w_1272,c_limit,f_webp,q_auto:good,fl_progressive:steep/https%3A%2F%2Fsubstack-post-media.s3.amazonaws.com%2Fpublic%2Fimages%2Fcc0637e4-145a-4b69-a936-7d57e89be14c_664x350.png 1272w, https://substackcdn.com/image/fetch/$s_!jOI9!,w_1456,c_limit,f_webp,q_auto:good,fl_progressive:steep/https%3A%2F%2Fsubstack-post-media.s3.amazonaws.com%2Fpublic%2Fimages%2Fcc0637e4-145a-4b69-a936-7d57e89be14c_664x350.png 1456w" sizes="100vw"><img src="https://substackcdn.com/image/fetch/$s_!jOI9!,w_1456,c_limit,f_auto,q_auto:good,fl_progressive:steep/https%3A%2F%2Fsubstack-post-media.s3.amazonaws.com%2Fpublic%2Fimages%2Fcc0637e4-145a-4b69-a936-7d57e89be14c_664x350.png" width="664" height="350" data-attrs="{&quot;src&quot;:&quot;https://substack-post-media.s3.amazonaws.com/public/images/cc0637e4-145a-4b69-a936-7d57e89be14c_664x350.png&quot;,&quot;srcNoWatermark&quot;:null,&quot;fullscreen&quot;:null,&quot;imageSize&quot;:null,&quot;height&quot;:350,&quot;width&quot;:664,&quot;resizeWidth&quot;:null,&quot;bytes&quot;:null,&quot;alt&quot;:&quot;De KNMI data opgeslagen als CSV&quot;,&quot;title&quot;:null,&quot;type&quot;:null,&quot;href&quot;:null,&quot;belowTheFold&quot;:true,&quot;topImage&quot;:false,&quot;internalRedirect&quot;:null,&quot;isProcessing&quot;:false,&quot;align&quot;:null,&quot;offset&quot;:false}" class="sizing-normal" alt="De KNMI data opgeslagen als CSV" title="De KNMI data opgeslagen als CSV" srcset="https://substackcdn.com/image/fetch/$s_!jOI9!,w_424,c_limit,f_auto,q_auto:good,fl_progressive:steep/https%3A%2F%2Fsubstack-post-media.s3.amazonaws.com%2Fpublic%2Fimages%2Fcc0637e4-145a-4b69-a936-7d57e89be14c_664x350.png 424w, https://substackcdn.com/image/fetch/$s_!jOI9!,w_848,c_limit,f_auto,q_auto:good,fl_progressive:steep/https%3A%2F%2Fsubstack-post-media.s3.amazonaws.com%2Fpublic%2Fimages%2Fcc0637e4-145a-4b69-a936-7d57e89be14c_664x350.png 848w, https://substackcdn.com/image/fetch/$s_!jOI9!,w_1272,c_limit,f_auto,q_auto:good,fl_progressive:steep/https%3A%2F%2Fsubstack-post-media.s3.amazonaws.com%2Fpublic%2Fimages%2Fcc0637e4-145a-4b69-a936-7d57e89be14c_664x350.png 1272w, https://substackcdn.com/image/fetch/$s_!jOI9!,w_1456,c_limit,f_auto,q_auto:good,fl_progressive:steep/https%3A%2F%2Fsubstack-post-media.s3.amazonaws.com%2Fpublic%2Fimages%2Fcc0637e4-145a-4b69-a936-7d57e89be14c_664x350.png 1456w" sizes="100vw" loading="lazy"></picture><div class="image-link-expand"><div class="pencraft pc-display-flex pc-gap-8 pc-reset"><button tabindex="0" type="button" class="pencraft pc-reset pencraft icon-container restack-image"><svg aria-hidden="true" width="20" height="20" viewBox="0 0 20 20" fill="none" stroke-width="1.5" stroke="var(--color-fg-primary)" stroke-linecap="round" stroke-linejoin="round" xmlns="http://www.w3.org/2000/svg"><g><path d="M2.53001 7.81595C3.49179 4.73911 6.43281 2.5 9.91173 2.5C13.1684 2.5 15.9537 4.46214 17.0852 7.23684L17.6179 8.67647M17.6179 8.67647L18.5002 4.26471M17.6179 8.67647L13.6473 6.91176M17.4995 12.1841C16.5378 15.2609 13.5967 17.5 10.1178 17.5C6.86118 17.5 4.07589 15.5379 2.94432 12.7632L2.41165 11.3235M2.41165 11.3235L1.5293 15.7353M2.41165 11.3235L6.38224 13.0882"></path></g></svg></button><button tabindex="0" type="button" class="pencraft pc-reset pencraft icon-container view-image"><svg xmlns="http://www.w3.org/2000/svg" width="20" height="20" viewBox="0 0 24 24" fill="none" stroke="currentColor" stroke-width="2" stroke-linecap="round" stroke-linejoin="round" class="lucide lucide-maximize2 lucide-maximize-2"><polyline points="15 3 21 3 21 9"></polyline><polyline points="9 21 3 21 3 15"></polyline><line x1="21" x2="14" y1="3" y2="10"></line><line x1="3" x2="10" y1="21" y2="14"></line></svg></button></div></div></div></a></figure></div><p>Je ziet een netjes komma-gescheiden bestand. Je kunt het nu ook openen in een spreadsheet programma zoals Microsoft Excel of LibreOffice Calc om het verder te inspecteren.</p><h2>Conclusie</h2><p>Je hebt geleerd dat je met een klein Python-script eenvoudig gegevens kunt downloaden met <code>requests</code> en ze vervolgens kunt opschonen. In dit voorbeeld worden de gegevens elke maand bijgewerkt. Met een eenvoudige <code>python main.py</code> download je zo de laatste gegevens en is het klaar voor gebruik. Uiteraard kun je het script naar eigen inzicht uitbreiden of aanpassen, bijvoorbeeld door de bestanden een naam met de huidige datum erin mee te geven of een paar statistieken te draaien.</p><p>Succes!</p>]]></content:encoded></item><item><title><![CDATA[Werken met pip en venv]]></title><description><![CDATA[Packages installeren is eenvoudig met pip. Maar: installeer niet globaal, maar in een virtuele omgeving! Lees hier het hoe, wat en waarom.]]></description><link>https://pythoncursus.substack.com/p/werken-met-pip-en-venv</link><guid isPermaLink="false">https://pythoncursus.substack.com/p/werken-met-pip-en-venv</guid><dc:creator><![CDATA[python-cursus.nl]]></dc:creator><pubDate>Mon, 12 Feb 2024 12:50:07 GMT</pubDate><enclosure url="https://substack-post-media.s3.amazonaws.com/public/images/67e62c53-b152-4bbb-bf48-099c55ab0b97_1024x1024.png" length="0" type="image/jpeg"/><content:encoded><![CDATA[<p>Je hoeft niet altijd het wiel zelf uit te vinden, ook in het programmeren niet. Veel functionaliteit is al een keer geschreven en goed herbruikbaar. Er zijn talloze <em>packages</em> en <em>libraries</em> die je kunt installeren en in je eigen project kunt gebruiken. Een groot deel van die <em>packages</em> kun je eenvoudig installeren met <code>pip</code>. Op <a href="https://pypi.org">pypi.org</a> vind je die <em>packages</em>.</p><p>Het is aan te raden om <em>packages</em> te installeren in een eigen omgeving behorende bij het project waaraan je werkt. Je kunt ze wel systeembreed installeren, maar dit kan voor problemen zorgen. Stel dat je aan twee projecten werkt, die beiden <a href="https://requests.readthedocs.io/en/latest/">requests</a> nodig hebben. Installeer je <code>requests</code> nu systeembreed, dan zijn beide projecten gedwongen altijd dezelfde versie van <code>requests</code> te gebruiken. Maar misschien kan het ene project nog niet naar de nieuwste versie, om wat voor reden dan ook. Dan ben je gedwongen in al je projecten op de oude versie te blijven.</p><p>Daarom kun je het best per project werken met een zogenaamde <strong>virtual environment</strong>. Een <em>virtual environment</em> is een afgeschermde omgeving per project, waarin de <em>packages</em> worden ge&#239;nstalleerd. In project A kun je zo de laatste versie van <code>requests</code> installeren, terwijl je het in project B houdt bij een oudere versie. Je maakt <em>virtual environments</em> met <code>venv</code>.</p><p>In deze tutorial leer je werken met <code>venv</code> en <code>pip</code>.</p><h2>Controleren of pip en venv beschikbaar zijn</h2><p>Voordat je verdergaat met de tutorial is het belangrijk te controleren of <code>pip</code> en <code>venv</code> beschikbaar zijn.</p><h3>pip</h3><p>Er zijn meerdere manieren om <em>packages</em> te installeren. Door Python zelf wordt <code>pip</code> aanbevolen, en sinds Python 3.4 is <code>pip</code> standaard onderdeel van Python-installaties. Het is (hierdoor) de meest gebruikte methode om <em>packages</em> te installeren. Als je een recente versie van Python (3.4 of hoger) hebt ge&#239;nstalleerd via <a href="https://python.org">python.org</a>, dan heb je <code>pip</code> tot je beschikking. Je kunt dit als volgt controleren:</p><p>shell</p><pre><code><code>python -m pip --help
</code></code></pre><p>Als je een uitleg krijgt, is <code>pip</code> beschikbaar. Zo niet, update dan Python.</p><h3>venv</h3><p>Er zijn ook meerdere manieren om <em>virtual environments</em> te maken, maar ook hier leer je weer hoe te werken met de aanbevolen en ingebouwde manier: <code>venv</code>. Net als bij <code>pip</code> geldt: als je een recente versie van Python hebt ge&#239;nstalleerd, is <code>venv</code> bijgesloten. Controleer dit met:</p><p>shell</p><pre><code><code>python -m venv --help
</code></code></pre><p>Als je een uitleg krijgt, is <code>venv</code> beschikbaar. Zo niet, update dan Python.</p><p>Als alles werkt, kun je verder met de tutorial.</p><h2>Virtual environments aanmaken</h2><p>Een <em>virtual environment</em> maak je aan met <code>venv</code>. Het maakt een afgeschermde omgeving op basis van de Python-versie waarmee je <code>venv</code> aanroept. Als je meerdere Python-versies hebt ge&#239;nstalleerd, kun je dus kiezen met welke Python-versie je de omgeving wilt aanmaken. Op mijn laptop heb ik bijvoorbeeld twee versies beschikbaar:</p><p>shell</p><pre><code><code>python --version  # 3.10.12
python3.12  --version  # 3.12.1
````

Ik wil met de standaardversie (3.10) een *virtual environment* maken. Het is mijn gewoonte om de *virtual environment* aan te maken in mijn projectmap. In mijn shell maak ik eerst een lege projectmap aan, en `cd` erheen.

```{.shell title="shell"}
cd projects
mkdir my-project &amp;&amp; cd my-project
</code></code></pre><p>Vervolgens maak ik een <em>virtual environment</em> aan:</p><p>shell</p><pre><code><code>python -m venv .venv
</code></code></pre><p>Ik geef de <em>virtual environment</em> de naam <code>.venv</code> (door de voorlopende punt is het een verborgen map), maar je kunt het elke andere naam geven. Gebruikelijk is wel om het <code>venv</code> of <code>.venv</code> te noemen.</p><p>De afgeschermde omgeving is nu gemaakt, <strong>maar nog niet actief</strong>. Dit is belangrijk. Voer je nu <code>python</code> uit, dan zal het nog steeds de systeembrede Python gebruiken. Om te werken binnen de afgeschermde omgeving, moet je het eerst activeren. Hoe je dit doet is afhankelijk van de shell die je gebruikt:</p><div class="captioned-image-container"><figure><a class="image-link image2 is-viewable-img" target="_blank" href="https://substackcdn.com/image/fetch/$s_!OYWE!,f_auto,q_auto:good,fl_progressive:steep/https%3A%2F%2Fsubstack-post-media.s3.amazonaws.com%2Fpublic%2Fimages%2Fca7a7945-2fdc-49db-af7b-4f2526609fe1_972x442.png" data-component-name="Image2ToDOM"><div class="image2-inset"><picture><source type="image/webp" srcset="https://substackcdn.com/image/fetch/$s_!OYWE!,w_424,c_limit,f_webp,q_auto:good,fl_progressive:steep/https%3A%2F%2Fsubstack-post-media.s3.amazonaws.com%2Fpublic%2Fimages%2Fca7a7945-2fdc-49db-af7b-4f2526609fe1_972x442.png 424w, https://substackcdn.com/image/fetch/$s_!OYWE!,w_848,c_limit,f_webp,q_auto:good,fl_progressive:steep/https%3A%2F%2Fsubstack-post-media.s3.amazonaws.com%2Fpublic%2Fimages%2Fca7a7945-2fdc-49db-af7b-4f2526609fe1_972x442.png 848w, https://substackcdn.com/image/fetch/$s_!OYWE!,w_1272,c_limit,f_webp,q_auto:good,fl_progressive:steep/https%3A%2F%2Fsubstack-post-media.s3.amazonaws.com%2Fpublic%2Fimages%2Fca7a7945-2fdc-49db-af7b-4f2526609fe1_972x442.png 1272w, https://substackcdn.com/image/fetch/$s_!OYWE!,w_1456,c_limit,f_webp,q_auto:good,fl_progressive:steep/https%3A%2F%2Fsubstack-post-media.s3.amazonaws.com%2Fpublic%2Fimages%2Fca7a7945-2fdc-49db-af7b-4f2526609fe1_972x442.png 1456w" sizes="100vw"><img src="https://substackcdn.com/image/fetch/$s_!OYWE!,w_1456,c_limit,f_auto,q_auto:good,fl_progressive:steep/https%3A%2F%2Fsubstack-post-media.s3.amazonaws.com%2Fpublic%2Fimages%2Fca7a7945-2fdc-49db-af7b-4f2526609fe1_972x442.png" width="972" height="442" data-attrs="{&quot;src&quot;:&quot;https://substack-post-media.s3.amazonaws.com/public/images/ca7a7945-2fdc-49db-af7b-4f2526609fe1_972x442.png&quot;,&quot;srcNoWatermark&quot;:null,&quot;fullscreen&quot;:null,&quot;imageSize&quot;:null,&quot;height&quot;:442,&quot;width&quot;:972,&quot;resizeWidth&quot;:null,&quot;bytes&quot;:null,&quot;alt&quot;:&quot;Activeer venv&quot;,&quot;title&quot;:null,&quot;type&quot;:null,&quot;href&quot;:null,&quot;belowTheFold&quot;:true,&quot;topImage&quot;:false,&quot;internalRedirect&quot;:null,&quot;isProcessing&quot;:false,&quot;align&quot;:null,&quot;offset&quot;:false}" class="sizing-normal" alt="Activeer venv" title="Activeer venv" srcset="https://substackcdn.com/image/fetch/$s_!OYWE!,w_424,c_limit,f_auto,q_auto:good,fl_progressive:steep/https%3A%2F%2Fsubstack-post-media.s3.amazonaws.com%2Fpublic%2Fimages%2Fca7a7945-2fdc-49db-af7b-4f2526609fe1_972x442.png 424w, https://substackcdn.com/image/fetch/$s_!OYWE!,w_848,c_limit,f_auto,q_auto:good,fl_progressive:steep/https%3A%2F%2Fsubstack-post-media.s3.amazonaws.com%2Fpublic%2Fimages%2Fca7a7945-2fdc-49db-af7b-4f2526609fe1_972x442.png 848w, https://substackcdn.com/image/fetch/$s_!OYWE!,w_1272,c_limit,f_auto,q_auto:good,fl_progressive:steep/https%3A%2F%2Fsubstack-post-media.s3.amazonaws.com%2Fpublic%2Fimages%2Fca7a7945-2fdc-49db-af7b-4f2526609fe1_972x442.png 1272w, https://substackcdn.com/image/fetch/$s_!OYWE!,w_1456,c_limit,f_auto,q_auto:good,fl_progressive:steep/https%3A%2F%2Fsubstack-post-media.s3.amazonaws.com%2Fpublic%2Fimages%2Fca7a7945-2fdc-49db-af7b-4f2526609fe1_972x442.png 1456w" sizes="100vw" loading="lazy"></picture><div class="image-link-expand"><div class="pencraft pc-display-flex pc-gap-8 pc-reset"><button tabindex="0" type="button" class="pencraft pc-reset pencraft icon-container restack-image"><svg aria-hidden="true" width="20" height="20" viewBox="0 0 20 20" fill="none" stroke-width="1.5" stroke="var(--color-fg-primary)" stroke-linecap="round" stroke-linejoin="round" xmlns="http://www.w3.org/2000/svg"><g><path d="M2.53001 7.81595C3.49179 4.73911 6.43281 2.5 9.91173 2.5C13.1684 2.5 15.9537 4.46214 17.0852 7.23684L17.6179 8.67647M17.6179 8.67647L18.5002 4.26471M17.6179 8.67647L13.6473 6.91176M17.4995 12.1841C16.5378 15.2609 13.5967 17.5 10.1178 17.5C6.86118 17.5 4.07589 15.5379 2.94432 12.7632L2.41165 11.3235M2.41165 11.3235L1.5293 15.7353M2.41165 11.3235L6.38224 13.0882"></path></g></svg></button><button tabindex="0" type="button" class="pencraft pc-reset pencraft icon-container view-image"><svg xmlns="http://www.w3.org/2000/svg" width="20" height="20" viewBox="0 0 24 24" fill="none" stroke="currentColor" stroke-width="2" stroke-linecap="round" stroke-linejoin="round" class="lucide lucide-maximize2 lucide-maximize-2"><polyline points="15 3 21 3 21 9"></polyline><polyline points="9 21 3 21 3 15"></polyline><line x1="21" x2="14" y1="3" y2="10"></line><line x1="3" x2="10" y1="21" y2="14"></line></svg></button></div></div></div></a></figure></div><p>Ik werk met Linux, dus ziet het er als volgt uit:</p><p>shell</p><pre><code><code>source .venv/bin/activate
</code></code></pre><p>Je kunt een omgeving weer deactiveren met <code>deactivate</code>.</p><h2>Packages installeren</h2><p>Nu je een actieve <em>virtual environment</em> hebt, kun je hierin <em>packages</em> installeren. Omdat je net bent beginnen, zijn er slechts twee <em>packages</em> ge&#239;nstalleerd, namelijk <code>pip</code> en <code>setuptools</code>. Deze worden standaard ge&#239;nstalleerd door <code>venv</code>.</p><p>shell</p><pre><code><code>pip list

Package    Version
---------- -------
pip        22.0.2
setuptools 59.6.0
</code></code></pre><p>Installeer nu <code>requests</code> en controleer en voer nogmaals <code>pip list</code> uit:</p><p>shell</p><pre><code><code>pip install requests

...

pip list

Package            Version
------------------ ----------
certifi            2023.11.17
charset-normalizer 3.3.2
idna               3.6
pip                22.0.2
requests           2.31.0
setuptools         59.6.0
urllib3            2.1.0
</code></code></pre><p>Je ziet dat er naast <code>requests</code> nog een aantal <em>packages</em> zijn ge&#239;nstalleerd. Dit komt omdat <code>requests</code> deze heeft aangemerkt als afhankelijkheden. Het installeren van <code>requests</code> zal dus ook deze afhankelijkheden installeren.</p><h2>Requirements opslaan</h2><p>Het is handig om de <em>packages</em> die je in je project gebruikt, te noteren zodat ze eenvoudig ge&#239;nstalleerd kunnen worden. Dit is handig voor jezelf, als je bijvoorbeeld je <em>virtual environment</em> weggooit en alles opnieuw wilt installeren. Maar het is zeker ook handig als met meerdere personen aan &#233;&#233;n project werkt, of als je jouw project later wilt publiceren. De gebruikers van je project weten dan welke afhankelijkheden ze moeten installeren.</p><p>De standaard manier hiervoor is om je afhankelijkheden op te slaan in <code>requirements.txt</code>. Dit kan eenvoudig met <code>pip</code>:</p><p>shell</p><pre><code><code>pip freeze &gt; requirements.txt
</code></code></pre><p>Het resultaat is een tekstbestandje met alle <em>packages</em> (en hun afhankelijkheden) die je hebt ge&#239;nstalleerd:</p><p>requirements.txt</p><pre><code><code># requirements.txt
certifi==2023.11.17
charset-normalizer==3.3.2
idna==3.6
requests==2.31.0
urllib3==2.1.0
</code></code></pre><p>In dit geval zijn alle afhankelijkheden vastgezet op een specifiek versie. Je kunt dit wijzigen, en bijvoorbeeld aangeven dat je <code>requests</code> vanaf versie 2 wilt hebben, maar niet hoger dan versie 3:</p><p>requirements.txt</p><pre><code><code>...
requests &gt;=2.0, &lt;3
...
</code></code></pre><p>Gooi je nu je <em>virtual environment</em> weg, dan kun je alles eenvoudig opnieuw installeren.</p><p>shell</p><pre><code><code># Verwijder .venv
deactivate
rm -rf .venv

# Maak .venv opnieuw aan en activeer
python -m venv .venv
source .venv/bin/activate

# Installeer alles opnieuw
pip install -r requirements.txt
</code></code></pre><p>Bekijk je nu weer de lijst met ge&#239;nstalleerde <em>packages</em>, dan is alles weer aanwezig:</p><p>shell</p><pre><code><code>pip list

Package            Version
------------------ ----------
certifi            2023.11.17
charset-normalizer 3.3.2
idna               3.6
pip                22.0.2
requests           2.31.0
setuptools         59.6.0
urllib3            2.1.0
</code></code></pre><h2>Verder lezen</h2><p>In deze tutorial leerde je de basis van het werken in een <em>virtual environment</em> en <em>packages</em> installeren met <code>pip</code>. Met die basis kun je nu verder, maar er is veel meer mogelijk:</p><ul><li><p><em>Packages</em> met een specifiek versie installeren</p></li><li><p>In <code>requirements.txt</code> aangeven dat je alles binnen de <em>major</em> versie wilt installeren</p></li><li><p>Ge&#239;nstalleerde <em>packages</em> upgraden met <code>pip</code></p></li><li><p><em>Packages</em> verwijderen</p></li><li><p>...</p></li></ul><p>De <a href="https://pip.pypa.io/en/stable/">documentatie van pip</a> is uitgebreid en goed leesbaar. Voor het aangeven van welke versie je wilt installeren, lees je <a href="https://packaging.python.org/en/latest/specifications/version-specifiers/#id4">deze paragraaf</a> op <a href="https://packaging.python.org/en/latest/">packaging.python.org</a>. Voor het werken met <code>venv</code> lees je <a href="https://docs.python.org/3/library/venv.html">de documentatie van python</a>.</p>]]></content:encoded></item></channel></rss>