File size: 58,387 Bytes
1cce1df
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
77
78
79
80
81
82
83
84
<!DOCTYPE html>
<html lang="en">
  <head>
    <meta charset="utf-8" />
    <link rel="icon" href="./favicon.ico" />
    <!-- Preload is necessary because we show these images when we disconnect from the server,
    but at that point we cannot load these images from the server -->
    <link rel="preload" href="./assets/gradient-yHQUC_QB.png" as="image" />
    <link rel="preload" href="./assets/noise-60BoTA8O.png" as="image" />
    <!-- Preload the fonts -->
    <link rel="preload" href="./assets/Lora-VariableFont_wght-B2ootaw-.ttf" as="font" crossorigin="anonymous" />
    <link rel="preload" href="./assets/PTSans-Regular-CxL0S8W7.ttf" as="font" crossorigin="anonymous" />
    <link rel="preload" href="./assets/PTSans-Bold-D9fedIX3.ttf" as="font" crossorigin="anonymous" />
    <link rel="preload" href="./assets/FiraMono-Regular-BTCkDNvf.ttf" as="font" crossorigin="anonymous" />
    <link rel="preload" href="./assets/FiraMono-Medium-DU3aDxX5.ttf" as="font" crossorigin="anonymous" />
    <link rel="preload" href="./assets/FiraMono-Bold-CLVRCuM9.ttf" as="font" crossorigin="anonymous" />

    <meta name="viewport" content="width=device-width, initial-scale=1" />
    <meta name="theme-color" content="#000000" />
    <meta name="description" content="a marimo app" />
    <link rel="apple-touch-icon" href="./apple-touch-icon.png" />
    <link rel="manifest" href="./manifest.json" />

    <script data-marimo="true">
      function __resizeIframe(obj) {
        var scrollbarHeight = 20; // Max between windows, mac, and linux

        function setHeight() {
          var element = obj.contentWindow.document.documentElement;
          // If there is no vertical scrollbar, we don't need to resize the iframe
          if (element.scrollHeight === element.clientHeight) {
            return;
          }

          // Create a new height that includes the scrollbar height if it's visible
          var hasHorizontalScrollbar = element.scrollWidth > element.clientWidth;
          var newHeight = element.scrollHeight + (hasHorizontalScrollbar ? scrollbarHeight : 0);

          // Only update the height if it's different from the current height
          if (obj.style.height !== `${newHeight}px`) {
            obj.style.height = `${newHeight}px`;
          }
        }

        // Resize the iframe to the height of the content and bottom scrollbar height
        setHeight();

        // Resize the iframe when the content changes
        const resizeObserver = new ResizeObserver((entries) => {
          setHeight();
        });
        resizeObserver.observe(obj.contentWindow.document.body);
      }
    </script>
    <marimo-filename hidden>notebook.py</marimo-filename>
    <marimo-mode data-mode='edit' hidden></marimo-mode>
    <marimo-version data-version='0.11.9' hidden></marimo-version>
    <marimo-user-config data-config='{"completion": {"activate_on_typing": true, "copilot": false}, "display": {"default_width": "medium", "dataframes": "rich", "code_editor_font_size": 14, "theme": "light", "cell_output": "above"}, "formatting": {"line_length": 79}, "keymap": {"preset": "default", "overrides": {}}, "runtime": {"auto_instantiate": true, "auto_reload": "off", "on_cell_change": "autorun", "watcher_on_save": "lazy", "output_max_bytes": 8000000, "std_stream_max_bytes": 1000000}, "save": {"autosave": "off", "autosave_delay": 1000, "format_on_save": false}, "package_management": {"manager": "pip"}, "server": {"browser": "default", "follow_symlink": false}, "snippets": {"custom_paths": [], "include_default_snippets": true}}' data-overrides='{}' hidden></marimo-user-config>
    <marimo-app-config data-config='{"width": "medium"}' hidden></marimo-app-config>
    <marimo-server-token data-token='123' hidden></marimo-server-token>
    <title>14 user defined functions</title>
    <script type="module" crossorigin src="./assets/index-BiV-b1K2.js"></script>
    <link rel="stylesheet" crossorigin href="./assets/index-DkqMrX_B.css">
  <marimo-wasm hidden=""></marimo-wasm>
    <script>
        if (window.location.protocol === 'file:') {
            alert('Warning: This file must be served by an HTTP server to function correctly.');
        }
    </script>
    
    <style>
        #save-button {
            display: none !important;
        }
        #filename-input {
            display: none !important;
        }
    </style>
    <marimo-code hidden="" data-show-code="false">import%20marimo%0A%0A__generated_with%20%3D%20%220.11.9%22%0Aapp%20%3D%20marimo.App(width%3D%22medium%22)%0A%0A%0A%40app.cell(hide_code%3DTrue)%0Adef%20_(mo)%3A%0A%20%20%20%20mo.md(%0A%20%20%20%20%20%20%20%20r%22%22%22%0A%20%20%20%20%20%20%20%20%23%20User-Defined%20Functions%0A%0A%20%20%20%20%20%20%20%20_By%20%5BP%C3%A9ter%20Ferenc%20Gyarmati%5D(http%3A%2F%2Fgithub.com%2Fpeter-gy)_.%0A%0A%20%20%20%20%20%20%20%20Throughout%20the%20previous%20chapters%2C%20you've%20seen%20how%20Polars%20provides%20a%20comprehensive%20set%20of%20built-in%20expressions%20for%20flexible%20data%20transformation.%20%20But%20what%20happens%20when%20you%20need%20something%20*more*%3F%20Perhaps%20your%20project%20has%20unique%20requirements%2C%20or%20you%20need%20to%20integrate%20functionality%20from%20an%20external%20Python%20library.%20This%20is%20where%20User-Defined%20Functions%20(UDFs)%20come%20into%20play%2C%20allowing%20you%20to%20extend%20Polars%20with%20your%20own%20custom%20logic.%0A%0A%20%20%20%20%20%20%20%20In%20this%20chapter%2C%20we'll%20weigh%20the%20performance%20trade-offs%20of%20UDFs%2C%20pinpoint%20situations%20where%20they're%20truly%20beneficial%2C%20and%20explore%20different%20ways%20to%20effectively%20incorporate%20them%20into%20your%20Polars%20workflows.%20We'll%20walk%20through%20a%20complete%2C%20practical%20example.%0A%20%20%20%20%20%20%20%20%22%22%22%0A%20%20%20%20)%0A%20%20%20%20return%0A%0A%0A%40app.cell(hide_code%3DTrue)%0Adef%20_(mo)%3A%0A%20%20%20%20mo.md(%0A%20%20%20%20%20%20%20%20r%22%22%22%0A%20%20%20%20%20%20%20%20%23%23%20%E2%9A%96%EF%B8%8F%20The%20Cost%20of%20UDFs%0A%0A%20%20%20%20%20%20%20%20%3E%20Performance%20vs.%20Flexibility%0A%0A%20%20%20%20%20%20%20%20Polars'%20built-in%20expressions%20are%20highly%20optimized%20for%20speed%20and%20parallel%20processing.%20User-defined%20functions%20(UDFs)%2C%20however%2C%20introduce%20a%20significant%20performance%20overhead%20because%20they%20rely%20on%20standard%20Python%20code%2C%20which%20often%20runs%20in%20a%20single%20thread%20and%20bypasses%20Polars'%20logical%20optimizations.%20Therefore%2C%20always%20prioritize%20native%20Polars%20operations%20*whenever%20possible*.%0A%0A%20%20%20%20%20%20%20%20However%2C%20UDFs%20become%20inevitable%20when%20you%20need%20to%3A%0A%0A%20%20%20%20%20%20%20%20-%20%20**Integrate%20external%20libraries%3A**%20%20Use%20functionality%20not%20directly%20available%20in%20Polars.%0A%20%20%20%20%20%20%20%20-%20%20**Implement%20custom%20logic%3A**%20Handle%20complex%20transformations%20that%20can't%20be%20easily%20expressed%20with%20Polars'%20built-in%20functions.%0A%0A%20%20%20%20%20%20%20%20Let's%20dive%20into%20a%20real-world%20project%20where%20UDFs%20were%20the%20only%20way%20to%20get%20the%20job%20done%2C%20demonstrating%20a%20scenario%20where%20native%20Polars%20expressions%20simply%20weren't%20sufficient.%0A%20%20%20%20%20%20%20%20%22%22%22%0A%20%20%20%20)%0A%20%20%20%20return%0A%0A%0A%40app.cell(hide_code%3DTrue)%0Adef%20_(mo)%3A%0A%20%20%20%20mo.md(%0A%20%20%20%20%20%20%20%20r%22%22%22%0A%20%20%20%20%20%20%20%20%23%23%20%F0%9F%93%8A%20Project%20Overview%0A%0A%20%20%20%20%20%20%20%20%3E%20Scraping%20and%20Analyzing%20Observable%20Notebook%20Statistics%0A%0A%20%20%20%20%20%20%20%20If%20you're%20into%20data%20visualization%2C%20you've%20probably%20seen%20%5BD3.js%5D(https%3A%2F%2Fd3js.org%2F)%20and%20%5BObservable%20Plot%5D(https%3A%2F%2Fobservablehq.com%2Fplot%2F).%20Both%20have%20extensive%20galleries%20showcasing%20amazing%20visualizations.%20Each%20gallery%20item%20is%20a%20standalone%20%5BObservable%20notebook%5D(https%3A%2F%2Fobservablehq.com%2Fdocumentation%2Fnotebooks%2F)%2C%20with%20metrics%20like%20stars%2C%20comments%2C%20and%20forks%20%E2%80%93%20indicators%20of%20popularity.%20But%20getting%20and%20analyzing%20these%20statistics%20directly%20isn't%20straightforward.%20We'll%20need%20to%20scrape%20the%20web.%0A%20%20%20%20%20%20%20%20%22%22%22%0A%20%20%20%20)%0A%20%20%20%20return%0A%0A%0A%40app.cell(hide_code%3DTrue)%0Adef%20_(mo)%3A%0A%20%20%20%20mo.hstack(%0A%20%20%20%20%20%20%20%20%5B%0A%20%20%20%20%20%20%20%20%20%20%20%20mo.image(%0A%20%20%20%20%20%20%20%20%20%20%20%20%20%20%20%20%22https%3A%2F%2Fminio.peter.gy%2Fstatic%2Fassets%2Fmarimo%2Flearn%2Fpolars%2F14_d3-gallery.png%3F0%22%2C%0A%20%20%20%20%20%20%20%20%20%20%20%20%20%20%20%20width%3D600%2C%0A%20%20%20%20%20%20%20%20%20%20%20%20%20%20%20%20caption%3D%22Screenshot%20of%20https%3A%2F%2Fobservablehq.com%2F%40d3%2Fgallery%22%2C%0A%20%20%20%20%20%20%20%20%20%20%20%20)%2C%0A%20%20%20%20%20%20%20%20%20%20%20%20mo.image(%0A%20%20%20%20%20%20%20%20%20%20%20%20%20%20%20%20%22https%3A%2F%2Fminio.peter.gy%2Fstatic%2Fassets%2Fmarimo%2Flearn%2Fpolars%2F14_plot-gallery.png%3F0%22%2C%0A%20%20%20%20%20%20%20%20%20%20%20%20%20%20%20%20width%3D600%2C%0A%20%20%20%20%20%20%20%20%20%20%20%20%20%20%20%20caption%3D%22Screenshot%20of%20https%3A%2F%2Fobservablehq.com%2F%40observablehq%2Fplot-gallery%22%2C%0A%20%20%20%20%20%20%20%20%20%20%20%20)%2C%0A%20%20%20%20%20%20%20%20%5D%0A%20%20%20%20)%0A%20%20%20%20return%0A%0A%0A%40app.cell(hide_code%3DTrue)%0Adef%20_(mo)%3A%0A%20%20%20%20mo.md(r%22%22%22Our%20goal%20is%20to%20use%20Polars%20UDFs%20to%20fetch%20the%20HTML%20content%20of%20these%20gallery%20pages.%20Then%2C%20we'll%20use%20the%20%60BeautifulSoup%60%20Python%20library%20to%20parse%20the%20HTML%20and%20extract%20the%20relevant%20metadata.%20%20After%20some%20data%20wrangling%20with%20native%20Polars%20expressions%2C%20we'll%20have%20a%20DataFrame%20listing%20each%20visualization%20notebook.%20Then%2C%20we'll%20use%20another%20UDF%20to%20retrieve%20the%20number%20of%20likes%2C%20forks%2C%20and%20comments%20for%20each%20notebook.%20Finally%2C%20we%20will%20create%20our%20own%20high-performance%20UDF%20to%20implement%20a%20custom%20notebook%20ranking%20scheme.%20This%20will%20involve%20multiple%20steps%2C%20showcasing%20different%20UDF%20approaches.%22%22%22)%0A%20%20%20%20return%0A%0A%0A%40app.cell(hide_code%3DTrue)%0Adef%20_(mo)%3A%0A%20%20%20%20mo.mermaid('''%0A%20%20%20%20graph%20LR%3B%0A%20%20%20%20%20%20%20%20url_df%20--%3E%20%7C%22UDF%3A%20Fetch%20HTML%22%7C%20html_df%0A%20%20%20%20%20%20%20%20html_df%20--%3E%20%7C%22UDF%3A%20Parse%20with%20BeautifulSoup%22%7C%20parsed_html_df%0A%20%20%20%20%20%20%20%20parsed_html_df%20--%3E%20%7C%22Native%20Polars%3A%20Extract%20Data%22%7C%20notebooks_df%0A%20%20%20%20%20%20%20%20notebooks_df%20--%3E%20%7C%22UDF%3A%20Get%20Notebook%20Stats%22%7C%20notebook_stats_df%0A%20%20%20%20%20%20%20%20notebook_stats_df%20--%3E%20%7C%22Numba%20UDF%3A%20Compute%20Popularity%22%7C%20notebook_popularity_df%0A%20%20%20%20''')%0A%20%20%20%20return%0A%0A%0A%40app.cell(hide_code%3DTrue)%0Adef%20_(mo)%3A%0A%20%20%20%20mo.md(r%22%22%22Our%20starting%20point%2C%20%60url_df%60%2C%20is%20a%20simple%20DataFrame%20with%20a%20single%20%60url%60%20column%20containing%20the%20URLs%20of%20the%20D3%20and%20Observable%20Plot%20gallery%20notebooks.%22%22%22)%0A%20%20%20%20return%0A%0A%0A%40app.cell(hide_code%3DTrue)%0Adef%20_(pl)%3A%0A%20%20%20%20url_df%20%3D%20pl.from_dict(%0A%20%20%20%20%20%20%20%20%7B%0A%20%20%20%20%20%20%20%20%20%20%20%20%22url%22%3A%20%5B%0A%20%20%20%20%20%20%20%20%20%20%20%20%20%20%20%20%22https%3A%2F%2Fobservablehq.com%2F%40d3%2Fgallery%22%2C%0A%20%20%20%20%20%20%20%20%20%20%20%20%20%20%20%20%22https%3A%2F%2Fobservablehq.com%2F%40observablehq%2Fplot-gallery%22%2C%0A%20%20%20%20%20%20%20%20%20%20%20%20%5D%0A%20%20%20%20%20%20%20%20%7D%0A%20%20%20%20)%0A%20%20%20%20url_df%0A%20%20%20%20return%20(url_df%2C)%0A%0A%0A%40app.cell(hide_code%3DTrue)%0Adef%20_(mo)%3A%0A%20%20%20%20mo.md(%0A%20%20%20%20%20%20%20%20r%22%22%22%0A%20%20%20%20%20%20%20%20%23%23%20%F0%9F%94%82%20Element-Wise%20UDFs%0A%0A%20%20%20%20%20%20%20%20%3E%20Processing%20Value%20by%20Value%0A%0A%20%20%20%20%20%20%20%20The%20most%20common%20way%20to%20use%20UDFs%20is%20to%20apply%20them%20element-wise.%20%20This%20means%20our%20custom%20function%20will%20execute%20for%20*each%20individual%20row*%20in%20a%20specified%20column.%20%20Our%20first%20task%20is%20to%20fetch%20the%20HTML%20content%20for%20each%20URL%20in%20%60url_df%60.%0A%0A%20%20%20%20%20%20%20%20We'll%20define%20a%20Python%20function%20that%20takes%20a%20%60url%60%20(a%20string)%20as%20input%2C%20uses%20the%20%60httpx%60%20library%20(an%20HTTP%20client)%20to%20fetch%20the%20content%2C%20and%20returns%20the%20HTML%20as%20a%20string.%20We%20then%20integrate%20this%20function%20into%20Polars%20using%20the%20%5B%60map_elements%60%5D(https%3A%2F%2Fdocs.pola.rs%2Fapi%2Fpython%2Fstable%2Freference%2Fexpressions%2Fapi%2Fpolars.Expr.map_elements.html)%20expression.%0A%0A%20%20%20%20%20%20%20%20You'll%20notice%20we%20have%20to%20explicitly%20specify%20the%20%60return_dtype%60.%20%20This%20is%20*crucial*.%20%20Polars%20doesn't%20automatically%20know%20what%20our%20custom%20function%20will%20return.%20%20We're%20responsible%20for%20defining%20the%20function's%20logic%20and%2C%20therefore%2C%20its%20output%20type.%20By%20providing%20the%20%60return_dtype%60%2C%20we%20help%20Polars%20maintain%20its%20internal%20representation%20of%20the%20DataFrame's%20schema%2C%20enabling%20query%20optimization.%20Think%20of%20it%20as%20giving%20Polars%20a%20%22heads-up%22%20about%20the%20data%20type%20it%20should%20expect.%0A%20%20%20%20%20%20%20%20%22%22%22%0A%20%20%20%20)%0A%20%20%20%20return%0A%0A%0A%40app.cell(hide_code%3DTrue)%0Adef%20_(httpx%2C%20pl%2C%20url_df)%3A%0A%20%20%20%20html_df%20%3D%20url_df.with_columns(%0A%20%20%20%20%20%20%20%20html%3Dpl.col(%22url%22).map_elements(%0A%20%20%20%20%20%20%20%20%20%20%20%20lambda%20url%3A%20httpx.get(url).text%2C%0A%20%20%20%20%20%20%20%20%20%20%20%20return_dtype%3Dpl.String%2C%0A%20%20%20%20%20%20%20%20)%0A%20%20%20%20)%0A%20%20%20%20html_df%0A%20%20%20%20return%20(html_df%2C)%0A%0A%0A%40app.cell(hide_code%3DTrue)%0Adef%20_(mo)%3A%0A%20%20%20%20mo.md(%0A%20%20%20%20%20%20%20%20r%22%22%22%0A%20%20%20%20%20%20%20%20Now%2C%20%60html_df%60%20holds%20the%20HTML%20for%20each%20URL.%20%20We%20need%20to%20parse%20it.%20Again%2C%20a%20UDF%20is%20the%20way%20to%20go.%20Parsing%20HTML%20with%20native%20Polars%20expressions%20would%20be%20a%20nightmare!%20Instead%2C%20we'll%20use%20the%20%5B%60beautifulsoup4%60%5D(https%3A%2F%2Fpypi.org%2Fproject%2Fbeautifulsoup4%2F)%20library%2C%20a%20standard%20tool%20for%20this.%0A%0A%20%20%20%20%20%20%20%20These%20Observable%20pages%20are%20built%20with%20%5BNext.js%5D(https%3A%2F%2Fnextjs.org%2F)%2C%20which%20helpfully%20serializes%20page%20properties%20as%20JSON%20within%20the%20HTML.%20This%20simplifies%20our%20UDF%3A%20we'll%20extract%20the%20raw%20JSON%20from%20the%20%60%3Cscript%20id%3D%22__NEXT_DATA__%22%20type%3D%22application%2Fjson%22%3E%60%20tag.%20We'll%20use%20%5B%60map_elements%60%5D(https%3A%2F%2Fdocs.pola.rs%2Fapi%2Fpython%2Fstable%2Freference%2Fexpressions%2Fapi%2Fpolars.Expr.map_elements.html)%20again.%20%20For%20clarity%2C%20we'll%20define%20this%20UDF%20as%20a%20named%20function%2C%20%60extract_nextjs_data%60%2C%20since%20it's%20a%20bit%20more%20complex%20than%20a%20simple%20HTTP%20request.%0A%20%20%20%20%20%20%20%20%22%22%22%0A%20%20%20%20)%0A%20%20%20%20return%0A%0A%0A%40app.cell(hide_code%3DTrue)%0Adef%20_(BeautifulSoup)%3A%0A%20%20%20%20def%20extract_nextjs_data(html%3A%20str)%20-%3E%20str%3A%0A%20%20%20%20%20%20%20%20soup%20%3D%20BeautifulSoup(html%2C%20%22html.parser%22)%0A%20%20%20%20%20%20%20%20script_tag%20%3D%20soup.find(%22script%22%2C%20id%3D%22__NEXT_DATA__%22)%0A%20%20%20%20%20%20%20%20return%20script_tag.text%0A%20%20%20%20return%20(extract_nextjs_data%2C)%0A%0A%0A%40app.cell(hide_code%3DTrue)%0Adef%20_(extract_nextjs_data%2C%20html_df%2C%20pl)%3A%0A%20%20%20%20parsed_html_df%20%3D%20html_df.select(%0A%20%20%20%20%20%20%20%20%22url%22%2C%0A%20%20%20%20%20%20%20%20next_data%3Dpl.col(%22html%22).map_elements(%0A%20%20%20%20%20%20%20%20%20%20%20%20extract_nextjs_data%2C%0A%20%20%20%20%20%20%20%20%20%20%20%20return_dtype%3Dpl.String%2C%0A%20%20%20%20%20%20%20%20)%2C%0A%20%20%20%20)%0A%20%20%20%20parsed_html_df%0A%20%20%20%20return%20(parsed_html_df%2C)%0A%0A%0A%40app.cell(hide_code%3DTrue)%0Adef%20_(mo)%3A%0A%20%20%20%20mo.md(r%22%22%22With%20some%20data%20wrangling%20of%20the%20raw%20JSON%20(using%20*native*%20Polars%20expressions!)%2C%20we%20get%20%60notebooks_df%60%2C%20containing%20the%20metadata%20for%20each%20notebook.%22%22%22)%0A%20%20%20%20return%0A%0A%0A%40app.cell(hide_code%3DTrue)%0Adef%20_(parsed_html_df%2C%20pl)%3A%0A%20%20%20%20notebooks_df%20%3D%20(%0A%20%20%20%20%20%20%20%20parsed_html_df.select(%0A%20%20%20%20%20%20%20%20%20%20%20%20%22url%22%2C%0A%20%20%20%20%20%20%20%20%20%20%20%20%23%20We%20extract%20the%20content%20of%20every%20cell%20present%20in%20the%20gallery%20notebooks%0A%20%20%20%20%20%20%20%20%20%20%20%20cell%3Dpl.col(%22next_data%22)%0A%20%20%20%20%20%20%20%20%20%20%20%20.str.json_path_match(%22%24.props.pageProps.initialNotebook.nodes%22)%0A%20%20%20%20%20%20%20%20%20%20%20%20.str.json_decode()%0A%20%20%20%20%20%20%20%20%20%20%20%20.list.eval(pl.element().struct.field(%22value%22))%2C%0A%20%20%20%20%20%20%20%20)%0A%20%20%20%20%20%20%20%20%23%20We%20want%20one%20row%20per%20cell%0A%20%20%20%20%20%20%20%20.explode(%22cell%22)%0A%20%20%20%20%20%20%20%20%23%20Only%20keep%20categorized%20notebook%20listing%20cells%20starting%20with%20H3%0A%20%20%20%20%20%20%20%20.filter(pl.col(%22cell%22).str.starts_with(%22%23%23%23%20%22))%0A%20%20%20%20%20%20%20%20%23%20Split%20up%20the%20cells%20into%20%5Bheading%2C%20description%2C%20config%5D%20sections%0A%20%20%20%20%20%20%20%20.with_columns(pl.col(%22cell%22).str.split(%22%5Cn%5Cn%22))%0A%20%20%20%20%20%20%20%20.select(%0A%20%20%20%20%20%20%20%20%20%20%20%20gallery_url%3D%22url%22%2C%0A%20%20%20%20%20%20%20%20%20%20%20%20%23%20Text%20after%20the%20'%23%23%23%20'%20heading%2C%20ignore%20'%3C!--'%20comments'%0A%20%20%20%20%20%20%20%20%20%20%20%20category%3Dpl.col(%22cell%22).list.get(0).str.extract(r%22%23%23%23%5Cs%2B(.*%3F)(%3F%3A%5Cs%2B%3C!--.*%3F--%3E%7C%24)%22)%2C%0A%20%20%20%20%20%20%20%20%20%20%20%20%23%20Paragraph%20after%20heading%0A%20%20%20%20%20%20%20%20%20%20%20%20description%3Dpl.col(%22cell%22)%0A%20%20%20%20%20%20%20%20%20%20%20%20.list.get(1)%0A%20%20%20%20%20%20%20%20%20%20%20%20.str.strip_chars(%22%20%22)%0A%20%20%20%20%20%20%20%20%20%20%20%20.str.replace_all(%22%5D(%2F%22%2C%20%22%5D(https%3A%2F%2Fobservablehq.com%2F%22%2C%20literal%3DTrue)%2C%0A%20%20%20%20%20%20%20%20%20%20%20%20%23%20Parsed%20notebook%20config%20from%20%24%7Bpreview(%5B%7B...%7D%5D)%7D%0A%20%20%20%20%20%20%20%20%20%20%20%20notebooks%3Dpl.col(%22cell%22)%0A%20%20%20%20%20%20%20%20%20%20%20%20.list.get(2)%0A%20%20%20%20%20%20%20%20%20%20%20%20.str.strip_prefix(%22%24%7Bpreviews(%5B%22)%0A%20%20%20%20%20%20%20%20%20%20%20%20.str.strip_suffix(%22%5D%7D)%7D%22)%0A%20%20%20%20%20%20%20%20%20%20%20%20.str.strip_chars(%22%20%5Cn%22)%0A%20%20%20%20%20%20%20%20%20%20%20%20.str.split(%22%7D%2C%22)%0A%20%20%20%20%20%20%20%20%20%20%20%20%23%20Simple%20regex-based%20attribute%20extraction%20from%20JS%2FJSON%20objects%20like%0A%20%20%20%20%20%20%20%20%20%20%20%20%23%20%60%60%60js%0A%20%20%20%20%20%20%20%20%20%20%20%20%23%20%7B%0A%20%20%20%20%20%20%20%20%20%20%20%20%23%20%20%20path%3A%20%22%40d3%2Fspilhaus-shoreline-map%22%2C%0A%20%20%20%20%20%20%20%20%20%20%20%20%23%20%20%20%22thumbnail%22%3A%20%2266a87355e205d820...%22%2C%0A%20%20%20%20%20%20%20%20%20%20%20%20%23%20%20%20title%3A%20%22Spilhaus%20shoreline%20map%22%2C%0A%20%20%20%20%20%20%20%20%20%20%20%20%23%20%20%20%22author%22%3A%20%22D3%22%0A%20%20%20%20%20%20%20%20%20%20%20%20%23%20%7D%0A%20%20%20%20%20%20%20%20%20%20%20%20%23%20%60%60%60%0A%20%20%20%20%20%20%20%20%20%20%20%20.list.eval(%0A%20%20%20%20%20%20%20%20%20%20%20%20%20%20%20%20pl.struct(%0A%20%20%20%20%20%20%20%20%20%20%20%20%20%20%20%20%20%20%20%20*(%0A%20%20%20%20%20%20%20%20%20%20%20%20%20%20%20%20%20%20%20%20%20%20%20%20pl.element()%0A%20%20%20%20%20%20%20%20%20%20%20%20%20%20%20%20%20%20%20%20%20%20%20%20.str.extract(f'(%3F%3A%22%7Bkey%7D%22%7C%7Bkey%7D)%5Cs*%3A%5Cs*%22(%5B%5E%22%5D*)%22')%0A%20%20%20%20%20%20%20%20%20%20%20%20%20%20%20%20%20%20%20%20%20%20%20%20.alias(key)%0A%20%20%20%20%20%20%20%20%20%20%20%20%20%20%20%20%20%20%20%20%20%20%20%20for%20key%20in%20%5B%22path%22%2C%20%22thumbnail%22%2C%20%22title%22%5D%0A%20%20%20%20%20%20%20%20%20%20%20%20%20%20%20%20%20%20%20%20)%0A%20%20%20%20%20%20%20%20%20%20%20%20%20%20%20%20)%0A%20%20%20%20%20%20%20%20%20%20%20%20)%2C%0A%20%20%20%20%20%20%20%20)%0A%20%20%20%20%20%20%20%20.explode(%22notebooks%22)%0A%20%20%20%20%20%20%20%20.unnest(%22notebooks%22)%0A%20%20%20%20%20%20%20%20.filter(pl.col(%22path%22).is_not_null())%0A%20%20%20%20%20%20%20%20%23%20Final%20projection%20to%20end%20up%20with%20directly%20usable%20values%0A%20%20%20%20%20%20%20%20.select(%0A%20%20%20%20%20%20%20%20%20%20%20%20pl.concat_str(%0A%20%20%20%20%20%20%20%20%20%20%20%20%20%20%20%20%5B%0A%20%20%20%20%20%20%20%20%20%20%20%20%20%20%20%20%20%20%20%20pl.lit(%22https%3A%2F%2Fstatic.observableusercontent.com%2Fthumbnail%2F%22)%2C%0A%20%20%20%20%20%20%20%20%20%20%20%20%20%20%20%20%20%20%20%20%22thumbnail%22%2C%0A%20%20%20%20%20%20%20%20%20%20%20%20%20%20%20%20%20%20%20%20pl.lit(%22.jpg%22)%2C%0A%20%20%20%20%20%20%20%20%20%20%20%20%20%20%20%20%5D%2C%0A%20%20%20%20%20%20%20%20%20%20%20%20).alias(%22notebook_thumbnail_src%22)%2C%0A%20%20%20%20%20%20%20%20%20%20%20%20%22category%22%2C%0A%20%20%20%20%20%20%20%20%20%20%20%20%22title%22%2C%0A%20%20%20%20%20%20%20%20%20%20%20%20%22description%22%2C%0A%20%20%20%20%20%20%20%20%20%20%20%20pl.concat_str(%0A%20%20%20%20%20%20%20%20%20%20%20%20%20%20%20%20%5Bpl.lit(%22https%3A%2F%2Fobservablehq.com%22)%2C%20%22path%22%5D%2C%20separator%3D%22%2F%22%0A%20%20%20%20%20%20%20%20%20%20%20%20).alias(%22notebook_url%22)%2C%0A%20%20%20%20%20%20%20%20)%0A%20%20%20%20)%0A%20%20%20%20notebooks_df%0A%20%20%20%20return%20(notebooks_df%2C)%0A%0A%0A%40app.cell(hide_code%3DTrue)%0Adef%20_(mo)%3A%0A%20%20%20%20mo.md(%0A%20%20%20%20%20%20%20%20r%22%22%22%0A%20%20%20%20%20%20%20%20%23%23%20%F0%9F%93%A6%20Batch-Wise%20UDFs%0A%0A%20%20%20%20%20%20%20%20%3E%20Processing%20Entire%20Series%0A%0A%20%20%20%20%20%20%20%20%60map_elements%60%20calls%20the%20UDF%20for%20*each%20row*.%20Fine%20for%20our%20tiny%2C%20two-rows-tall%20%60url_df%60.%20But%20%60notebooks_df%60%20has%20almost%20400%20rows!%20Individual%20HTTP%20requests%20for%20each%20would%20be%20painfully%20slow.%0A%0A%20%20%20%20%20%20%20%20We%20want%20stats%20for%20each%20notebook%20in%20%60notebooks_df%60.%20To%20avoid%20sequential%20requests%2C%20we'll%20use%20Polars'%20%5B%60map_batches%60%5D(https%3A%2F%2Fdocs.pola.rs%2Fapi%2Fpython%2Fstable%2Freference%2Fexpressions%2Fapi%2Fpolars.Expr.map_batches.html).%20This%20lets%20us%20process%20an%20*entire%20Series*%20(a%20column)%20at%20once.%0A%0A%20%20%20%20%20%20%20%20Our%20UDF%2C%20%60fetch_html_batch%60%2C%20will%20take%20a%20*Series*%20of%20URLs%20and%20use%20%60asyncio%60%20to%20make%20concurrent%20requests%20%E2%80%93%20a%20huge%20performance%20boost.%0A%20%20%20%20%20%20%20%20%22%22%22%0A%20%20%20%20)%0A%20%20%20%20return%0A%0A%0A%40app.cell(hide_code%3DTrue)%0Adef%20_(Iterable%2C%20asyncio%2C%20httpx%2C%20mo)%3A%0A%20%20%20%20async%20def%20_fetch_html_batch(urls%3A%20Iterable%5Bstr%5D)%20-%3E%20tuple%5Bstr%2C%20...%5D%3A%0A%20%20%20%20%20%20%20%20async%20with%20httpx.AsyncClient(timeout%3D15)%20as%20client%3A%0A%20%20%20%20%20%20%20%20%20%20%20%20res%20%3D%20await%20asyncio.gather(*(client.get(url)%20for%20url%20in%20urls))%0A%20%20%20%20%20%20%20%20%20%20%20%20return%20tuple((r.text%20for%20r%20in%20res))%0A%0A%0A%20%20%20%20%40mo.cache%0A%20%20%20%20def%20fetch_html_batch(urls%3A%20Iterable%5Bstr%5D)%20-%3E%20tuple%5Bstr%2C%20...%5D%3A%0A%20%20%20%20%20%20%20%20return%20asyncio.run(_fetch_html_batch(urls))%0A%20%20%20%20return%20(fetch_html_batch%2C)%0A%0A%0A%40app.cell(hide_code%3DTrue)%0Adef%20_(mo)%3A%0A%20%20%20%20mo.callout(%0A%20%20%20%20%20%20%20%20mo.md(%22%22%22%0A%20%20%20%20Since%20%60fetch_html_batch%60%20is%20a%20pure%20Python%20function%20and%20performs%20multiple%20network%20requests%2C%20it's%20a%20good%20candidate%20for%20caching.%20We%20use%20%5B%60mo.cache%60%5D(https%3A%2F%2Fdocs.marimo.io%2Fapi%2Fcaching%2F%23marimo.cache)%20to%20avoid%20redundant%20requests%20to%20the%20same%20URL.%20This%20is%20a%20simple%20way%20to%20improve%20performance%20without%20modifying%20the%20core%20logic.%0A%20%20%20%20%22%22%22%0A%20%20%20%20%20%20%20%20)%2C%0A%20%20%20%20%20%20%20%20kind%3D%22info%22%2C%0A%20%20%20%20)%0A%20%20%20%20return%0A%0A%0A%40app.cell(hide_code%3DTrue)%0Adef%20_(mo%2C%20notebooks_df)%3A%0A%20%20%20%20category%20%3D%20mo.ui.dropdown(%0A%20%20%20%20%20%20%20%20notebooks_df.sort(%22category%22).get_column(%22category%22)%2C%0A%20%20%20%20%20%20%20%20value%3D%22Maps%22%2C%0A%20%20%20%20)%0A%20%20%20%20return%20(category%2C)%0A%0A%0A%40app.cell(hide_code%3DTrue)%0Adef%20_(category%2C%20extract_nextjs_data%2C%20fetch_html_batch%2C%20notebooks_df%2C%20pl)%3A%0A%20%20%20%20notebook_stats_df%20%3D%20(%0A%20%20%20%20%20%20%20%20%23%20Setting%20filter%20upstream%20to%20limit%20number%20of%20concurrent%20HTTP%20requests%0A%20%20%20%20%20%20%20%20notebooks_df.filter(category%3Dcategory.value)%0A%20%20%20%20%20%20%20%20.with_columns(%0A%20%20%20%20%20%20%20%20%20%20%20%20notebook_html%3Dpl.col(%22notebook_url%22)%0A%20%20%20%20%20%20%20%20%20%20%20%20.map_batches(fetch_html_batch%2C%20return_dtype%3Dpl.List(pl.String))%0A%20%20%20%20%20%20%20%20%20%20%20%20.explode()%0A%20%20%20%20%20%20%20%20)%0A%20%20%20%20%20%20%20%20.with_columns(%0A%20%20%20%20%20%20%20%20%20%20%20%20notebook_data%3Dpl.col(%22notebook_html%22)%0A%20%20%20%20%20%20%20%20%20%20%20%20.map_elements(%0A%20%20%20%20%20%20%20%20%20%20%20%20%20%20%20%20extract_nextjs_data%2C%0A%20%20%20%20%20%20%20%20%20%20%20%20%20%20%20%20return_dtype%3Dpl.String%2C%0A%20%20%20%20%20%20%20%20%20%20%20%20)%0A%20%20%20%20%20%20%20%20%20%20%20%20.str.json_path_match(%22%24.props.pageProps.initialNotebook%22)%0A%20%20%20%20%20%20%20%20%20%20%20%20.str.json_decode()%0A%20%20%20%20%20%20%20%20)%0A%20%20%20%20%20%20%20%20.drop(%22notebook_html%22)%0A%20%20%20%20%20%20%20%20.with_columns(%0A%20%20%20%20%20%20%20%20%20%20%20%20*%5B%0A%20%20%20%20%20%20%20%20%20%20%20%20%20%20%20%20pl.col(%22notebook_data%22).struct.field(key).alias(key)%0A%20%20%20%20%20%20%20%20%20%20%20%20%20%20%20%20for%20key%20in%20%5B%22likes%22%2C%20%22forks%22%2C%20%22comments%22%2C%20%22license%22%5D%0A%20%20%20%20%20%20%20%20%20%20%20%20%5D%0A%20%20%20%20%20%20%20%20)%0A%20%20%20%20%20%20%20%20.drop(%22notebook_data%22)%0A%20%20%20%20%20%20%20%20.with_columns(pl.col(%22comments%22).list.len())%0A%20%20%20%20%20%20%20%20.select(%0A%20%20%20%20%20%20%20%20%20%20%20%20pl.exclude(%22description%22%2C%20%22notebook_url%22)%2C%0A%20%20%20%20%20%20%20%20%20%20%20%20%22description%22%2C%0A%20%20%20%20%20%20%20%20%20%20%20%20%22notebook_url%22%2C%0A%20%20%20%20%20%20%20%20)%0A%20%20%20%20%20%20%20%20.sort(%22likes%22%2C%20descending%3DTrue)%0A%20%20%20%20)%0A%20%20%20%20return%20(notebook_stats_df%2C)%0A%0A%0A%40app.cell(hide_code%3DTrue)%0Adef%20_(mo%2C%20notebook_stats_df)%3A%0A%20%20%20%20notebooks%20%3D%20mo.ui.table(notebook_stats_df%2C%20selection%3D'single'%2C%20initial_selection%3D%5B2%5D%2C%20page_size%3D5)%0A%20%20%20%20notebook_height%20%3D%20mo.ui.slider(start%3D400%2C%20stop%3D2000%2C%20value%3D825%2C%20step%3D25%2C%20show_value%3DTrue%2C%20label%3D'Notebook%20Height')%0A%20%20%20%20return%20notebook_height%2C%20notebooks%0A%0A%0A%40app.cell(hide_code%3DTrue)%0Adef%20_()%3A%0A%20%20%20%20def%20nb_iframe(notebook_url%3A%20str%2C%20height%3D825)%20-%3E%20str%3A%0A%20%20%20%20%20%20%20%20embed_url%20%3D%20notebook_url.replace(%0A%20%20%20%20%20%20%20%20%20%20%20%20%22https%3A%2F%2Fobservablehq.com%22%2C%20%22https%3A%2F%2Fobservablehq.com%2Fembed%22%0A%20%20%20%20%20%20%20%20)%0A%20%20%20%20%20%20%20%20return%20f'%3Ciframe%20width%3D%22100%25%22%20height%3D%22%7Bheight%7D%22%20frameborder%3D%220%22%20src%3D%22%7Bembed_url%7D%3Fcell%3D*%22%3E%3C%2Fiframe%3E'%0A%20%20%20%20return%20(nb_iframe%2C)%0A%0A%0A%40app.cell(hide_code%3DTrue)%0Adef%20_(mo)%3A%0A%20%20%20%20mo.md(r%22%22%22Now%20that%20we%20have%20access%20to%20notebook-level%20statistics%2C%20we%20can%20rank%20the%20visualizations%20by%20the%20number%20of%20likes%20they%20received%20%26%20display%20them%20interactively.%22%22%22)%0A%20%20%20%20return%0A%0A%0A%40app.cell(hide_code%3DTrue)%0Adef%20_(mo)%3A%0A%20%20%20%20mo.callout(%22%F0%9F%92%A1%20Explore%20the%20visualizations%20by%20paging%20through%20the%20table%20below%20and%20selecting%20any%20of%20its%20rows.%22)%0A%20%20%20%20return%0A%0A%0A%40app.cell(hide_code%3DTrue)%0Adef%20_(category%2C%20mo%2C%20nb_iframe%2C%20notebook_height%2C%20notebooks)%3A%0A%20%20%20%20notebook%20%3D%20notebooks.value.to_dicts()%5B0%5D%0A%20%20%20%20mo.vstack(%0A%20%20%20%20%20%20%20%20%5B%0A%20%20%20%20%20%20%20%20%20%20%20%20mo.hstack(%5Bcategory%2C%20notebook_height%5D)%2C%0A%20%20%20%20%20%20%20%20%20%20%20%20notebooks%2C%0A%20%20%20%20%20%20%20%20%20%20%20%20mo.md(f%22%7Bnotebook%5B'description'%5D%7D%22)%2C%0A%20%20%20%20%20%20%20%20%20%20%20%20mo.md('---')%2C%0A%20%20%20%20%20%20%20%20%20%20%20%20mo.md(nb_iframe(notebook%5B%22notebook_url%22%5D%2C%20notebook_height.value))%2C%0A%20%20%20%20%20%20%20%20%5D%0A%20%20%20%20)%0A%20%20%20%20return%20(notebook%2C)%0A%0A%0A%40app.cell(hide_code%3DTrue)%0Adef%20_(mo)%3A%0A%20%20%20%20mo.md(%0A%20%20%20%20%20%20%20%20r%22%22%22%0A%20%20%20%20%20%20%20%20%23%23%20%E2%9A%99%EF%B8%8F%20Row-Wise%20UDFs%0A%0A%20%20%20%20%20%20%20%20%3E%20Accessing%20All%20Columns%20at%20Once%0A%0A%20%20%20%20%20%20%20%20Sometimes%2C%20you%20need%20to%20work%20with%20*all*%20columns%20of%20a%20row%20at%20once.%20%20This%20is%20where%20%5B%60map_rows%60%5D(https%3A%2F%2Fdocs.pola.rs%2Fapi%2Fpython%2Fstable%2Freference%2Fdataframe%2Fapi%2Fpolars.DataFrame.map_rows.html)%20comes%20in.%20It%20operates%20directly%20on%20the%20DataFrame%2C%20passing%20each%20row%20to%20your%20UDF%20*as%20a%20tuple*.%0A%0A%20%20%20%20%20%20%20%20Below%2C%20%60create_notebook_summary%60%20takes%20a%20row%20from%20%60notebook_stats_df%60%20(as%20a%20tuple)%20and%20returns%20a%20formatted%20Markdown%20string%20summarizing%20the%20notebook's%20key%20stats.%20%20We're%20essentially%20reducing%20the%20DataFrame%20to%20a%20single%20column.%20%20While%20this%20*could*%20be%20done%20with%20native%20Polars%20expressions%2C%20it%20would%20be%20much%20more%20cumbersome.%20This%20example%20demonstrates%20a%20case%20where%20a%20row-wise%20UDF%20simplifies%20the%20code%2C%20even%20if%20the%20underlying%20operation%20isn't%20inherently%20complex.%0A%20%20%20%20%20%20%20%20%22%22%22%0A%20%20%20%20)%0A%20%20%20%20return%0A%0A%0A%40app.cell(hide_code%3DTrue)%0Adef%20_()%3A%0A%20%20%20%20def%20create_notebook_summary(row%3A%20tuple)%20-%3E%20str%3A%0A%20%20%20%20%20%20%20%20(%0A%20%20%20%20%20%20%20%20%20%20%20%20thumbnail_src%2C%0A%20%20%20%20%20%20%20%20%20%20%20%20category%2C%0A%20%20%20%20%20%20%20%20%20%20%20%20title%2C%0A%20%20%20%20%20%20%20%20%20%20%20%20likes%2C%0A%20%20%20%20%20%20%20%20%20%20%20%20forks%2C%0A%20%20%20%20%20%20%20%20%20%20%20%20comments%2C%0A%20%20%20%20%20%20%20%20%20%20%20%20license%2C%0A%20%20%20%20%20%20%20%20%20%20%20%20description%2C%0A%20%20%20%20%20%20%20%20%20%20%20%20notebook_url%2C%0A%20%20%20%20%20%20%20%20)%20%3D%20row%0A%20%20%20%20%20%20%20%20return%20(%0A%20%20%20%20%20%20%20%20%20%20%20%20f%22%22%22%0A%20%20%20%20%23%23%23%20%5B%7Btitle%7D%5D(%7Bnotebook_url%7D)%0A%0A%20%20%20%20%3Cdiv%20style%3D%22display%3A%20grid%3B%20grid-template-columns%3A%201fr%201fr%3B%20gap%3A%2012px%3B%20margin%3A%2012px%200%3B%22%3E%0A%20%20%20%20%20%20%20%20%3Cdiv%3E%E2%AD%90%20%3Cstrong%3ELikes%3A%3C%2Fstrong%3E%20%7Blikes%7D%3C%2Fdiv%3E%0A%20%20%20%20%20%20%20%20%3Cdiv%3E%E2%86%97%EF%B8%8F%20%3Cstrong%3EForks%3A%3C%2Fstrong%3E%20%7Bforks%7D%3C%2Fdiv%3E%0A%20%20%20%20%20%20%20%20%3Cdiv%3E%F0%9F%92%AC%20%3Cstrong%3EComments%3A%3C%2Fstrong%3E%20%7Bcomments%7D%3C%2Fdiv%3E%0A%20%20%20%20%20%20%20%20%3Cdiv%3E%E2%9A%96%EF%B8%8F%20%3Cstrong%3ELicense%3A%3C%2Fstrong%3E%20%7Blicense%7D%3C%2Fdiv%3E%0A%20%20%20%20%3C%2Fdiv%3E%0A%0A%20%20%20%20%3Ca%20href%3D%22%7Bnotebook_url%7D%22%20target%3D%22_blank%22%3E%0A%20%20%20%20%20%20%20%20%3Cimg%20src%3D%22%7Bthumbnail_src%7D%22%20style%3D%22height%3A%20300px%3B%22%20%2F%3E%0A%20%20%20%20%3Ca%2F%3E%0A%20%20%20%20%22%22%22.strip('%5Cn')%0A%20%20%20%20%20%20%20%20)%0A%20%20%20%20return%20(create_notebook_summary%2C)%0A%0A%0A%40app.cell(hide_code%3DTrue)%0Adef%20_(create_notebook_summary%2C%20notebook_stats_df%2C%20pl)%3A%0A%20%20%20%20notebook_summary_df%20%3D%20notebook_stats_df.map_rows(%0A%20%20%20%20%20%20%20%20create_notebook_summary%2C%0A%20%20%20%20%20%20%20%20return_dtype%3Dpl.String%2C%0A%20%20%20%20).rename(%7B%22map%22%3A%20%22summary%22%7D)%0A%20%20%20%20notebook_summary_df.head(1)%0A%20%20%20%20return%20(notebook_summary_df%2C)%0A%0A%0A%40app.cell(hide_code%3DTrue)%0Adef%20_(mo)%3A%0A%20%20%20%20mo.callout(%22%F0%9F%92%A1%20You%20can%20explore%20individual%20notebook%20statistics%20through%20the%20carousel.%20Discover%20the%20visualization's%20source%20code%20by%20clicking%20the%20notebook%20title%20or%20the%20thumbnail.%22)%0A%20%20%20%20return%0A%0A%0A%40app.cell(hide_code%3DTrue)%0Adef%20_(mo%2C%20notebook_summary_df)%3A%0A%20%20%20%20mo.carousel(%0A%20%20%20%20%20%20%20%20%5B%0A%20%20%20%20%20%20%20%20%20%20%20%20mo.lazy(mo.md(summary))%0A%20%20%20%20%20%20%20%20%20%20%20%20for%20summary%20in%20notebook_summary_df.get_column(%22summary%22)%0A%20%20%20%20%20%20%20%20%5D%0A%20%20%20%20)%0A%20%20%20%20return%0A%0A%0A%40app.cell(hide_code%3DTrue)%0Adef%20_(mo)%3A%0A%20%20%20%20mo.md(%0A%20%20%20%20%20%20%20%20r%22%22%22%0A%20%20%20%20%20%20%20%20%23%23%20%F0%9F%9A%80%20Higher-performance%20UDFs%0A%0A%20%20%20%20%20%20%20%20%3E%20Leveraging%20Numba%20to%20Make%20Python%20Fast%0A%0A%20%20%20%20%20%20%20%20Python%20code%20doesn't%20*always*%20mean%20slow%20code.%20While%20UDFs%20*often*%20introduce%20performance%20overhead%2C%20there%20are%20exceptions.%20NumPy's%20universal%20functions%20(%5B%60ufuncs%60%5D(https%3A%2F%2Fnumpy.org%2Fdoc%2Fstable%2Freference%2Fufuncs.html))%20and%20generalized%20universal%20functions%20(%5B%60gufuncs%60%5D(https%3A%2F%2Fnumpy.org%2Fneps%2Fnep-0005-generalized-ufuncs.html))%20provide%20high-performance%20operations%20on%20NumPy%20arrays%2C%20thanks%20to%20low-level%20implementations.%0A%0A%20%20%20%20%20%20%20%20But%20NumPy's%20built-in%20functions%20are%20predefined.%20We%20can't%20easily%20use%20them%20for%20*custom*%20logic.%20Enter%20%5B%60numba%60%5D(https%3A%2F%2Fnumba.pydata.org%2F).%20%20Numba%20is%20a%20just-in-time%20(JIT)%20compiler%20that%20translates%20Python%20functions%20into%20optimized%20machine%20code%20*at%20runtime*.%20It%20provides%20decorators%20like%20%5B%60numba.guvectorize%60%5D(https%3A%2F%2Fnumba.readthedocs.io%2Fen%2Fstable%2Fuser%2Fvectorize.html%23the-guvectorize-decorator)%20that%20let%20us%20create%20our%20*own*%20high-performance%20%60gufuncs%60%20%E2%80%93%20*without*%20writing%20low-level%20code!%0A%20%20%20%20%20%20%20%20%22%22%22%0A%20%20%20%20)%0A%20%20%20%20return%0A%0A%0A%40app.cell(hide_code%3DTrue)%0Adef%20_(mo)%3A%0A%20%20%20%20mo.md(%0A%20%20%20%20%20%20%20%20r%22%22%22%0A%20%20%20%20%20%20%20%20Let's%20create%20a%20custom%20popularity%20metric%20to%20rank%20notebooks%2C%20considering%20likes%2C%20forks%2C%20*and*%20comments%20(not%20just%20likes).%20%20We'll%20define%20%60weighted_popularity_numba%60%2C%20decorated%20with%20%60%40numba.guvectorize%60.%20%20The%20decorator%20arguments%20specify%20that%20we're%20taking%20three%20integer%20vectors%20of%20length%20%60n%60%20and%20returning%20a%20float%20vector%20of%20length%20%60n%60.%0A%0A%20%20%20%20%20%20%20%20The%20weighted%20popularity%20score%20for%20each%20notebook%20is%20calculated%20using%20the%20following%20formula%3A%0A%0A%20%20%20%20%20%20%20%20%24%24%0A%20%20%20%20%20%20%20%20%5Cbegin%7Bequation%7D%0A%20%20%20%20%20%20%20%20%5Ctext%7Bscore%7D_i%20%3D%20w_l%20%5Ccdot%20l_i%5E%7Bf%7D%20%2B%20w_f%20%5Ccdot%20f_i%5E%7Bf%7D%20%2B%20w_c%20%5Ccdot%20c_i%5E%7Bf%7D%0A%20%20%20%20%20%20%20%20%5Cend%7Bequation%7D%0A%20%20%20%20%20%20%20%20%24%24%0A%0A%20%20%20%20%20%20%20%20with%3A%0A%20%20%20%20%20%20%20%20%22%22%22%0A%20%20%20%20)%0A%20%20%20%20return%0A%0A%0A%40app.cell(hide_code%3DTrue)%0Adef%20_(mo%2C%20non_linear_factor%2C%20weight_comments%2C%20weight_forks%2C%20weight_likes)%3A%0A%20%20%20%20mo.md(rf%22%22%22%0A%20%20%20%20%7C%20Symbol%20%7C%20Description%20%7C%0A%20%20%20%20%7C--------%7C-------------%7C%0A%20%20%20%20%7C%20%24%5Ctext%7B%7Bscore%7D%7D_i%24%20%7C%20Popularity%20score%20for%20the%20*i*-th%20notebook%20%7C%0A%20%20%20%20%7C%20%24w_l%20%3D%20%7Bweight_likes.value%7D%24%20%7C%20Weight%20for%20likes%20%7C%0A%20%20%20%20%7C%20%24l_i%24%20%7C%20Number%20of%20likes%20for%20the%20*i*-th%20notebook%20%7C%0A%20%20%20%20%7C%20%24w_f%20%3D%20%7Bweight_forks.value%7D%24%20%7C%20Weight%20for%20forks%20%7C%0A%20%20%20%20%7C%20%24f_i%24%20%7C%20Number%20of%20forks%20for%20the%20*i*-th%20notebook%20%7C%0A%20%20%20%20%7C%20%24w_c%20%3D%20%7Bweight_comments.value%7D%24%20%7C%20Weight%20for%20comments%20%7C%0A%20%20%20%20%7C%20%24c_i%24%20%7C%20Number%20of%20comments%20for%20the%20*i*-th%20notebook%20%7C%0A%20%20%20%20%7C%20%24f%20%3D%20%7Bnon_linear_factor.value%7D%24%20%7C%20Non-linear%20factor%20(exponent)%20%7C%0A%20%20%20%20%22%22%22)%0A%20%20%20%20return%0A%0A%0A%40app.cell(hide_code%3DTrue)%0Adef%20_(mo)%3A%0A%20%20%20%20weight_likes%20%3D%20mo.ui.slider(%0A%20%20%20%20%20%20%20%20start%3D0.1%2C%0A%20%20%20%20%20%20%20%20stop%3D1%2C%0A%20%20%20%20%20%20%20%20value%3D0.5%2C%0A%20%20%20%20%20%20%20%20step%3D0.1%2C%0A%20%20%20%20%20%20%20%20show_value%3DTrue%2C%0A%20%20%20%20%20%20%20%20label%3D%22%E2%AD%90%20Weight%20for%20Likes%22%2C%0A%20%20%20%20)%0A%20%20%20%20weight_forks%20%3D%20mo.ui.slider(%0A%20%20%20%20%20%20%20%20start%3D0.1%2C%0A%20%20%20%20%20%20%20%20stop%3D1%2C%0A%20%20%20%20%20%20%20%20value%3D0.3%2C%0A%20%20%20%20%20%20%20%20step%3D0.1%2C%0A%20%20%20%20%20%20%20%20show_value%3DTrue%2C%0A%20%20%20%20%20%20%20%20label%3D%22%E2%86%97%EF%B8%8F%20Weight%20for%20Forks%22%2C%0A%20%20%20%20)%0A%20%20%20%20weight_comments%20%3D%20mo.ui.slider(%0A%20%20%20%20%20%20%20%20start%3D0.1%2C%0A%20%20%20%20%20%20%20%20stop%3D1%2C%0A%20%20%20%20%20%20%20%20value%3D0.5%2C%0A%20%20%20%20%20%20%20%20step%3D0.1%2C%0A%20%20%20%20%20%20%20%20show_value%3DTrue%2C%0A%20%20%20%20%20%20%20%20label%3D%22%F0%9F%92%AC%20Weight%20for%20Comments%22%2C%0A%20%20%20%20)%0A%20%20%20%20non_linear_factor%20%3D%20mo.ui.slider(%0A%20%20%20%20%20%20%20%20start%3D1%2C%0A%20%20%20%20%20%20%20%20stop%3D2%2C%0A%20%20%20%20%20%20%20%20value%3D1.2%2C%0A%20%20%20%20%20%20%20%20step%3D0.1%2C%0A%20%20%20%20%20%20%20%20show_value%3DTrue%2C%0A%20%20%20%20%20%20%20%20label%3D%22%F0%9F%8E%A2%20Non-Linear%20Factor%22%2C%0A%20%20%20%20)%0A%20%20%20%20return%20non_linear_factor%2C%20weight_comments%2C%20weight_forks%2C%20weight_likes%0A%0A%0A%40app.cell(hide_code%3DTrue)%0Adef%20_(%0A%20%20%20%20non_linear_factor%2C%0A%20%20%20%20np%2C%0A%20%20%20%20numba%2C%0A%20%20%20%20weight_comments%2C%0A%20%20%20%20weight_forks%2C%0A%20%20%20%20weight_likes%2C%0A)%3A%0A%20%20%20%20w_l%20%3D%20weight_likes.value%0A%20%20%20%20w_f%20%3D%20weight_forks.value%0A%20%20%20%20w_c%20%3D%20weight_comments.value%0A%20%20%20%20nlf%20%3D%20non_linear_factor.value%0A%0A%0A%20%20%20%20%40numba.guvectorize(%0A%20%20%20%20%20%20%20%20%5B(numba.int64%5B%3A%5D%2C%20numba.int64%5B%3A%5D%2C%20numba.int64%5B%3A%5D%2C%20numba.float64%5B%3A%5D)%5D%2C%0A%20%20%20%20%20%20%20%20%22(n)%2C%20(n)%2C%20(n)%20-%3E%20(n)%22%2C%0A%20%20%20%20)%0A%20%20%20%20def%20weighted_popularity_numba(%0A%20%20%20%20%20%20%20%20likes%3A%20np.ndarray%2C%0A%20%20%20%20%20%20%20%20forks%3A%20np.ndarray%2C%0A%20%20%20%20%20%20%20%20comments%3A%20np.ndarray%2C%0A%20%20%20%20%20%20%20%20out%3A%20np.ndarray%2C%0A%20%20%20%20)%3A%0A%20%20%20%20%20%20%20%20for%20i%20in%20range(likes.shape%5B0%5D)%3A%0A%20%20%20%20%20%20%20%20%20%20%20%20out%5Bi%5D%20%3D%20(%0A%20%20%20%20%20%20%20%20%20%20%20%20%20%20%20%20w_l%20*%20(likes%5Bi%5D%20**%20nlf)%0A%20%20%20%20%20%20%20%20%20%20%20%20%20%20%20%20%2B%20w_f%20*%20(forks%5Bi%5D%20**%20nlf)%0A%20%20%20%20%20%20%20%20%20%20%20%20%20%20%20%20%2B%20w_c%20*%20(comments%5Bi%5D%20**%20nlf)%0A%20%20%20%20%20%20%20%20%20%20%20%20)%0A%20%20%20%20return%20nlf%2C%20w_c%2C%20w_f%2C%20w_l%2C%20weighted_popularity_numba%0A%0A%0A%40app.cell(hide_code%3DTrue)%0Adef%20_(mo)%3A%0A%20%20%20%20mo.md(r%22%22%22We%20apply%20our%20JIT-compiled%20UDF%20using%20%60map_batches%60%2C%20as%20before.%20%20The%20key%20is%20that%20we're%20passing%20entire%20columns%20directly%20to%20%60weighted_popularity_numba%60.%20Polars%20and%20Numba%20handle%20the%20conversion%20to%20NumPy%20arrays%20behind%20the%20scenes.%20This%20direct%20integration%20is%20a%20major%20benefit%20of%20using%20%60guvectorize%60.%22%22%22)%0A%20%20%20%20return%0A%0A%0A%40app.cell(hide_code%3DTrue)%0Adef%20_(notebook_stats_df%2C%20pl%2C%20weighted_popularity_numba)%3A%0A%20%20%20%20notebook_popularity_df%20%3D%20(%0A%20%20%20%20%20%20%20%20notebook_stats_df.select(%0A%20%20%20%20%20%20%20%20%20%20%20%20pl.col(%22notebook_thumbnail_src%22).alias(%22thumbnail%22)%2C%0A%20%20%20%20%20%20%20%20%20%20%20%20%22title%22%2C%0A%20%20%20%20%20%20%20%20%20%20%20%20%22likes%22%2C%0A%20%20%20%20%20%20%20%20%20%20%20%20%22forks%22%2C%0A%20%20%20%20%20%20%20%20%20%20%20%20%22comments%22%2C%0A%20%20%20%20%20%20%20%20%20%20%20%20popularity%3Dpl.struct(%5B%22likes%22%2C%20%22forks%22%2C%20%22comments%22%5D).map_batches(%0A%20%20%20%20%20%20%20%20%20%20%20%20%20%20%20%20lambda%20obj%3A%20weighted_popularity_numba(%0A%20%20%20%20%20%20%20%20%20%20%20%20%20%20%20%20%20%20%20%20obj.struct.field(%22likes%22)%2C%0A%20%20%20%20%20%20%20%20%20%20%20%20%20%20%20%20%20%20%20%20obj.struct.field(%22forks%22)%2C%0A%20%20%20%20%20%20%20%20%20%20%20%20%20%20%20%20%20%20%20%20obj.struct.field(%22comments%22)%2C%0A%20%20%20%20%20%20%20%20%20%20%20%20%20%20%20%20)%2C%0A%20%20%20%20%20%20%20%20%20%20%20%20%20%20%20%20return_dtype%3Dpl.Float64%2C%0A%20%20%20%20%20%20%20%20%20%20%20%20)%2C%0A%20%20%20%20%20%20%20%20%20%20%20%20url%3D%22notebook_url%22%2C%0A%20%20%20%20%20%20%20%20)%0A%20%20%20%20)%0A%20%20%20%20return%20(notebook_popularity_df%2C)%0A%0A%0A%40app.cell(hide_code%3DTrue)%0Adef%20_(mo)%3A%0A%20%20%20%20mo.callout(%22%F0%9F%92%A1%20Adjust%20the%20hyperparameters%20of%20the%20popularity%20ranking%20UDF.%20How%20do%20the%20weights%20and%20non-linear%20factor%20affect%20the%20notebook%20rankings%3F%22)%0A%20%20%20%20return%0A%0A%0A%40app.cell(hide_code%3DTrue)%0Adef%20_(%0A%20%20%20%20mo%2C%0A%20%20%20%20non_linear_factor%2C%0A%20%20%20%20notebook_popularity_df%2C%0A%20%20%20%20weight_comments%2C%0A%20%20%20%20weight_forks%2C%0A%20%20%20%20weight_likes%2C%0A)%3A%0A%20%20%20%20mo.vstack(%0A%20%20%20%20%20%20%20%20%5B%0A%20%20%20%20%20%20%20%20%20%20%20%20mo.hstack(%5Bweight_likes%2C%20weight_forks%5D)%2C%0A%20%20%20%20%20%20%20%20%20%20%20%20mo.hstack(%5Bweight_comments%2C%20non_linear_factor%5D)%2C%0A%20%20%20%20%20%20%20%20%20%20%20%20notebook_popularity_df%2C%0A%20%20%20%20%20%20%20%20%5D%0A%20%20%20%20)%0A%20%20%20%20return%0A%0A%0A%40app.cell(hide_code%3DTrue)%0Adef%20_(mo)%3A%0A%20%20%20%20mo.md(r%22%22%22As%20the%20slope%20chart%20below%20demonstrates%2C%20this%20new%20ranking%20strategy%20significantly%20changes%20the%20notebook%20order%2C%20as%20it%20considers%20forks%20and%20comments%2C%20not%20just%20likes.%22%22%22)%0A%20%20%20%20return%0A%0A%0A%40app.cell(hide_code%3DTrue)%0Adef%20_(alt%2C%20notebook_popularity_df%2C%20pl)%3A%0A%20%20%20%20notebook_ranks_df%20%3D%20(%0A%20%20%20%20%20%20%20%20notebook_popularity_df.sort(%22likes%22%2C%20descending%3DTrue)%0A%20%20%20%20%20%20%20%20.with_row_index(%22rank_by_likes%22)%0A%20%20%20%20%20%20%20%20.with_columns(pl.col(%22rank_by_likes%22)%20%2B%201)%0A%20%20%20%20%20%20%20%20.sort(%22popularity%22%2C%20descending%3DTrue)%0A%20%20%20%20%20%20%20%20.with_row_index(%22rank_by_popularity%22)%0A%20%20%20%20%20%20%20%20.with_columns(pl.col(%22rank_by_popularity%22)%20%2B%201)%0A%20%20%20%20%20%20%20%20.select(%22thumbnail%22%2C%20%22title%22%2C%20%22rank_by_popularity%22%2C%20%22rank_by_likes%22)%0A%20%20%20%20%20%20%20%20.unpivot(%0A%20%20%20%20%20%20%20%20%20%20%20%20%5B%22rank_by_popularity%22%2C%20%22rank_by_likes%22%5D%2C%0A%20%20%20%20%20%20%20%20%20%20%20%20index%3D%22title%22%2C%0A%20%20%20%20%20%20%20%20%20%20%20%20variable_name%3D%22strategy%22%2C%0A%20%20%20%20%20%20%20%20%20%20%20%20value_name%3D%22rank%22%2C%0A%20%20%20%20%20%20%20%20)%0A%20%20%20%20)%0A%0A%20%20%20%20%23%20Slope%20chart%20to%20visualize%20rank%20differences%20by%20strategy%0A%20%20%20%20lines%20%3D%20notebook_ranks_df.plot.line(%0A%20%20%20%20%20%20%20%20x%3D%22strategy%3AO%22%2C%0A%20%20%20%20%20%20%20%20y%3D%22rank%3AQ%22%2C%0A%20%20%20%20%20%20%20%20color%3D%22title%3AN%22%2C%0A%20%20%20%20)%0A%20%20%20%20points%20%3D%20notebook_ranks_df.plot.point(%0A%20%20%20%20%20%20%20%20x%3D%22strategy%3AO%22%2C%0A%20%20%20%20%20%20%20%20y%3D%22rank%3AQ%22%2C%0A%20%20%20%20%20%20%20%20color%3Dalt.Color(%22title%3AN%22%2C%20legend%3DNone)%2C%0A%20%20%20%20%20%20%20%20fill%3D%22title%3AN%22%2C%0A%20%20%20%20)%0A%20%20%20%20(points%20%2B%20lines).properties(width%3D400)%0A%20%20%20%20return%20lines%2C%20notebook_ranks_df%2C%20points%0A%0A%0A%40app.cell(hide_code%3DTrue)%0Adef%20_(mo)%3A%0A%20%20%20%20mo.md(%0A%20%20%20%20%20%20%20%20r%22%22%22%0A%20%20%20%20%20%20%20%20%23%23%20%E2%8F%B1%EF%B8%8F%20Quantifying%20the%20Overhead%0A%0A%20%20%20%20%20%20%20%20%3E%20UDF%20Performance%20Comparison%0A%0A%20%20%20%20%20%20%20%20To%20truly%20understand%20the%20performance%20implications%20of%20using%20UDFs%2C%20let's%20conduct%20a%20benchmark.%20%20We'll%20create%20a%20DataFrame%20with%20random%20numbers%20and%20perform%20the%20same%20numerical%20operation%20using%20four%20different%20methods%3A%0A%0A%20%20%20%20%20%20%20%201.%20**Native%20Polars%3A**%20Using%20Polars'%20built-in%20expressions.%0A%20%20%20%20%20%20%20%202.%20**%60map_elements%60%3A**%20%20Applying%20a%20Python%20function%20element-wise.%0A%20%20%20%20%20%20%20%203.%20**%60map_batches%60%3A**%20**Applying**%20a%20Python%20function%20to%20the%20entire%20Series.%0A%20%20%20%20%20%20%20%204.%20**%60map_batches%60%20with%20Numba%3A**%20Applying%20a%20JIT-compiled%20function%20to%20batches%2C%20similar%20to%20a%20generalized%20universal%20function.%0A%0A%20%20%20%20%20%20%20%20We'll%20use%20a%20simple%2C%20but%20non-trivial%2C%20calculation%3A%20%20%60result%20%3D%20(x%20*%202.5%20%2B%205)%20%2F%20(x%20%2B%201)%60.%20This%20involves%20multiplication%2C%20addition%2C%20and%20division%2C%20giving%20us%20a%20realistic%20representation%20of%20a%20common%20numerical%20operation.%20We'll%20use%20the%20%60timeit%60%20module%2C%20to%20accurately%20measure%20execution%20times%20over%20multiple%20trials.%0A%20%20%20%20%20%20%20%20%22%22%22%0A%20%20%20%20)%0A%20%20%20%20return%0A%0A%0A%40app.cell(hide_code%3DTrue)%0Adef%20_(mo)%3A%0A%20%20%20%20mo.callout(%22%F0%9F%92%A1%20Tweak%20the%20benchmark%20parameters%20to%20explore%20how%20execution%20times%20change%20with%20different%20sample%20sizes%20and%20trial%20counts.%20Do%20you%20notice%20anything%20surprising%20as%20you%20decrease%20the%20number%20of%20samples%3F%22)%0A%20%20%20%20return%0A%0A%0A%40app.cell(hide_code%3DTrue)%0Adef%20_(benchmark_plot%2C%20mo%2C%20num_samples%2C%20num_trials)%3A%0A%20%20%20%20mo.vstack(%0A%20%20%20%20%20%20%20%20%5B%0A%20%20%20%20%20%20%20%20%20%20%20%20mo.hstack(%5Bnum_samples%2C%20num_trials%5D)%2C%0A%20%20%20%20%20%20%20%20%20%20%20%20mo.md(%0A%20%20%20%20%20%20%20%20%20%20%20%20%20%20%20%20f%22%22%22---%0A%20%20%20%20Performance%20comparison%20over%20**%7Bnum_trials.value%3A%2C%7D%20trials**%20with%20**%7Bnum_samples.value%3A%2C%7D%20samples**.%0A%0A%20%20%20%20%3E%20Lower%20execution%20times%20are%20better.%0A%20%20%20%20%22%22%22%0A%20%20%20%20%20%20%20%20%20%20%20%20)%2C%0A%20%20%20%20%20%20%20%20%20%20%20%20benchmark_plot%2C%0A%20%20%20%20%20%20%20%20%5D%0A%20%20%20%20)%0A%20%20%20%20return%0A%0A%0A%40app.cell(hide_code%3DTrue)%0Adef%20_(mo)%3A%0A%20%20%20%20mo.md(%0A%20%20%20%20%20%20%20%20r%22%22%22%0A%20%20%20%20%20%20%20%20As%20anticipated%2C%20the%20%60Batch-Wise%20UDF%20(Python)%60%20and%20%60Element-Wise%20UDF%60%20exhibit%20significantly%20worse%20performance%2C%20essentially%20acting%20as%20pure-Python%20for-each%20loops.%20%20%0A%0A%20%20%20%20%20%20%20%20However%2C%20when%20Python%20serves%20as%20an%20interface%20to%20lower-level%2C%20high-performance%20libraries%2C%20we%20observe%20substantial%20improvements.%20The%20%60Batch-Wise%20UDF%20(NumPy)%60%20lags%20behind%20both%20%60Batch-Wise%20UDF%20(Numba)%60%20and%20%60Native%20Polars%60%2C%20but%20it%20still%20represents%20a%20considerable%20improvement%20over%20pure-Python%20UDFs%20due%20to%20its%20vectorized%20computations.%20%0A%0A%20%20%20%20%20%20%20%20Numba's%20Just-In-Time%20(JIT)%20compilation%20delivers%20a%20dramatic%20performance%20boost%2C%20achieving%20speeds%20comparable%20to%20native%20Polars%20expressions.%20This%20demonstrates%20that%20UDFs%2C%20particularly%20when%20combined%20with%20tools%20like%20Numba%2C%20don't%20inevitably%20lead%20to%20bottlenecks%20in%20numerical%20computations.%0A%20%20%20%20%20%20%20%20%22%22%22%0A%20%20%20%20)%0A%20%20%20%20return%0A%0A%0A%40app.cell(hide_code%3DTrue)%0Adef%20_(mo)%3A%0A%20%20%20%20num_samples%20%3D%20mo.ui.slider(%0A%20%20%20%20%20%20%20%20start%3D1_000%2C%0A%20%20%20%20%20%20%20%20stop%3D1_000_000%2C%0A%20%20%20%20%20%20%20%20value%3D250_000%2C%0A%20%20%20%20%20%20%20%20step%3D1000%2C%0A%20%20%20%20%20%20%20%20show_value%3DTrue%2C%0A%20%20%20%20%20%20%20%20debounce%3DTrue%2C%0A%20%20%20%20%20%20%20%20label%3D%22Number%20of%20Samples%22%2C%0A%20%20%20%20)%0A%20%20%20%20num_trials%20%3D%20mo.ui.slider(%0A%20%20%20%20%20%20%20%20start%3D50%2C%0A%20%20%20%20%20%20%20%20stop%3D1_000%2C%0A%20%20%20%20%20%20%20%20value%3D100%2C%0A%20%20%20%20%20%20%20%20step%3D50%2C%0A%20%20%20%20%20%20%20%20show_value%3DTrue%2C%0A%20%20%20%20%20%20%20%20debounce%3DTrue%2C%0A%20%20%20%20%20%20%20%20label%3D%22Number%20of%20Trials%22%2C%0A%20%20%20%20)%0A%20%20%20%20return%20num_samples%2C%20num_trials%0A%0A%0A%40app.cell(hide_code%3DTrue)%0Adef%20_(np%2C%20num_samples%2C%20pl)%3A%0A%20%20%20%20rng%20%3D%20np.random.default_rng(42)%0A%20%20%20%20sample_df%20%3D%20pl.from_dict(%7B%22x%22%3A%20rng.random(num_samples.value)%7D)%0A%20%20%20%20return%20rng%2C%20sample_df%0A%0A%0A%40app.cell(hide_code%3DTrue)%0Adef%20_(np%2C%20num_trials%2C%20numba%2C%20pl%2C%20sample_df%2C%20timeit)%3A%0A%20%20%20%20def%20run_native()%3A%0A%20%20%20%20%20%20%20%20sample_df.with_columns(%0A%20%20%20%20%20%20%20%20%20%20%20%20result_native%3D(pl.col(%22x%22)%20*%202.5%20%2B%205)%20%2F%20(pl.col(%22x%22)%20%2B%201)%0A%20%20%20%20%20%20%20%20)%0A%0A%0A%20%20%20%20def%20_calculate_elementwise(x%3A%20float)%20-%3E%20float%3A%0A%20%20%20%20%20%20%20%20return%20(x%20*%202.5%20%2B%205)%20%2F%20(x%20%2B%201)%0A%0A%0A%20%20%20%20def%20run_map_elements()%3A%0A%20%20%20%20%20%20%20%20sample_df.with_columns(%0A%20%20%20%20%20%20%20%20%20%20%20%20result_map_elements%3Dpl.col(%22x%22).map_elements(%0A%20%20%20%20%20%20%20%20%20%20%20%20%20%20%20%20_calculate_elementwise%2C%0A%20%20%20%20%20%20%20%20%20%20%20%20%20%20%20%20return_dtype%3Dpl.Float64%2C%0A%20%20%20%20%20%20%20%20%20%20%20%20)%0A%20%20%20%20%20%20%20%20)%0A%0A%0A%20%20%20%20def%20_calculate_batchwise_numpy(x_series%3A%20pl.Series)%20-%3E%20pl.Series%3A%0A%20%20%20%20%20%20%20%20x_array%20%3D%20x_series.to_numpy()%0A%20%20%20%20%20%20%20%20result_array%20%3D%20(x_array%20*%202.5%20%2B%205)%20%2F%20(x_array%20%2B%201)%0A%20%20%20%20%20%20%20%20return%20pl.Series(result_array)%0A%0A%0A%20%20%20%20def%20run_map_batches_numpy()%3A%0A%20%20%20%20%20%20%20%20sample_df.with_columns(%0A%20%20%20%20%20%20%20%20%20%20%20%20result_map_batches_numpy%3Dpl.col(%22x%22).map_batches(%0A%20%20%20%20%20%20%20%20%20%20%20%20%20%20%20%20_calculate_batchwise_numpy%2C%0A%20%20%20%20%20%20%20%20%20%20%20%20%20%20%20%20return_dtype%3Dpl.Float64%2C%0A%20%20%20%20%20%20%20%20%20%20%20%20)%0A%20%20%20%20%20%20%20%20)%0A%0A%0A%20%20%20%20def%20_calculate_batchwise_python(x_series%3A%20pl.Series)%20-%3E%20pl.Series%3A%0A%20%20%20%20%20%20%20%20x_array%20%3D%20x_series.to_list()%0A%20%20%20%20%20%20%20%20result_array%20%3D%20%5B_calculate_elementwise(x)%20for%20x%20in%20x_array%5D%0A%20%20%20%20%20%20%20%20return%20pl.Series(result_array)%0A%0A%0A%20%20%20%20def%20run_map_batches_python()%3A%0A%20%20%20%20%20%20%20%20sample_df.with_columns(%0A%20%20%20%20%20%20%20%20%20%20%20%20result_map_batches_python%3Dpl.col(%22x%22).map_batches(%0A%20%20%20%20%20%20%20%20%20%20%20%20%20%20%20%20_calculate_batchwise_python%2C%0A%20%20%20%20%20%20%20%20%20%20%20%20%20%20%20%20return_dtype%3Dpl.Float64%2C%0A%20%20%20%20%20%20%20%20%20%20%20%20)%0A%20%20%20%20%20%20%20%20)%0A%0A%0A%20%20%20%20%40numba.guvectorize(%5B(numba.float64%5B%3A%5D%2C%20numba.float64%5B%3A%5D)%5D%2C%20%22(n)%20-%3E%20(n)%22)%0A%20%20%20%20def%20_calculate_batchwise_numba(x%3A%20np.ndarray%2C%20out%3A%20np.ndarray)%3A%0A%20%20%20%20%20%20%20%20for%20i%20in%20range(x.shape%5B0%5D)%3A%0A%20%20%20%20%20%20%20%20%20%20%20%20out%5Bi%5D%20%3D%20(x%5Bi%5D%20*%202.5%20%2B%205)%20%2F%20(x%5Bi%5D%20%2B%201)%0A%0A%0A%20%20%20%20def%20run_map_batches_numba()%3A%0A%20%20%20%20%20%20%20%20sample_df.with_columns(%0A%20%20%20%20%20%20%20%20%20%20%20%20result_map_batches_numba%3Dpl.col(%22x%22).map_batches(%0A%20%20%20%20%20%20%20%20%20%20%20%20%20%20%20%20_calculate_batchwise_numba%2C%0A%20%20%20%20%20%20%20%20%20%20%20%20%20%20%20%20return_dtype%3Dpl.Float64%2C%0A%20%20%20%20%20%20%20%20%20%20%20%20)%0A%20%20%20%20%20%20%20%20)%0A%0A%0A%20%20%20%20def%20time_method(callable_name%3A%20str%2C%20number%3Dnum_trials.value)%20-%3E%20float%3A%0A%20%20%20%20%20%20%20%20fn%20%3D%20globals()%5Bcallable_name%5D%0A%20%20%20%20%20%20%20%20return%20timeit.timeit(fn%2C%20number%3Dnumber)%0A%20%20%20%20return%20(%0A%20%20%20%20%20%20%20%20run_map_batches_numba%2C%0A%20%20%20%20%20%20%20%20run_map_batches_numpy%2C%0A%20%20%20%20%20%20%20%20run_map_batches_python%2C%0A%20%20%20%20%20%20%20%20run_map_elements%2C%0A%20%20%20%20%20%20%20%20run_native%2C%0A%20%20%20%20%20%20%20%20time_method%2C%0A%20%20%20%20)%0A%0A%0A%40app.cell(hide_code%3DTrue)%0Adef%20_(alt%2C%20pl%2C%20time_method)%3A%0A%20%20%20%20benchmark_df%20%3D%20pl.from_dicts(%0A%20%20%20%20%20%20%20%20%5B%0A%20%20%20%20%20%20%20%20%20%20%20%20%7B%0A%20%20%20%20%20%20%20%20%20%20%20%20%20%20%20%20%22title%22%3A%20%22Native%20Polars%22%2C%0A%20%20%20%20%20%20%20%20%20%20%20%20%20%20%20%20%22callable_name%22%3A%20%22run_native%22%2C%0A%20%20%20%20%20%20%20%20%20%20%20%20%7D%2C%0A%20%20%20%20%20%20%20%20%20%20%20%20%7B%0A%20%20%20%20%20%20%20%20%20%20%20%20%20%20%20%20%22title%22%3A%20%22Element-Wise%20UDF%22%2C%0A%20%20%20%20%20%20%20%20%20%20%20%20%20%20%20%20%22callable_name%22%3A%20%22run_map_elements%22%2C%0A%20%20%20%20%20%20%20%20%20%20%20%20%7D%2C%0A%20%20%20%20%20%20%20%20%20%20%20%20%7B%0A%20%20%20%20%20%20%20%20%20%20%20%20%20%20%20%20%22title%22%3A%20%22Batch-Wise%20UDF%20(NumPy)%22%2C%0A%20%20%20%20%20%20%20%20%20%20%20%20%20%20%20%20%22callable_name%22%3A%20%22run_map_batches_numpy%22%2C%0A%20%20%20%20%20%20%20%20%20%20%20%20%7D%2C%0A%20%20%20%20%20%20%20%20%20%20%20%20%7B%0A%20%20%20%20%20%20%20%20%20%20%20%20%20%20%20%20%22title%22%3A%20%22Batch-Wise%20UDF%20(Python)%22%2C%0A%20%20%20%20%20%20%20%20%20%20%20%20%20%20%20%20%22callable_name%22%3A%20%22run_map_batches_python%22%2C%0A%20%20%20%20%20%20%20%20%20%20%20%20%7D%2C%0A%20%20%20%20%20%20%20%20%20%20%20%20%7B%0A%20%20%20%20%20%20%20%20%20%20%20%20%20%20%20%20%22title%22%3A%20%22Batch-Wise%20UDF%20(Numba)%22%2C%0A%20%20%20%20%20%20%20%20%20%20%20%20%20%20%20%20%22callable_name%22%3A%20%22run_map_batches_numba%22%2C%0A%20%20%20%20%20%20%20%20%20%20%20%20%7D%2C%0A%20%20%20%20%20%20%20%20%5D%0A%20%20%20%20).with_columns(%0A%20%20%20%20%20%20%20%20time%3Dpl.col(%22callable_name%22).map_elements(%0A%20%20%20%20%20%20%20%20%20%20%20%20time_method%2C%20return_dtype%3Dpl.Float64%0A%20%20%20%20%20%20%20%20)%0A%20%20%20%20)%0A%0A%20%20%20%20benchmark_plot%20%3D%20benchmark_df.plot.bar(%0A%20%20%20%20%20%20%20%20x%3Dalt.X(%22title%3AN%22%2C%20title%3D%22Method%22%2C%20sort%3D%22-y%22)%2C%0A%20%20%20%20%20%20%20%20y%3Dalt.Y(%22time%3AQ%22%2C%20title%3D%22Execution%20Time%20(s)%22%2C%20axis%3Dalt.Axis(format%3D%22.3f%22))%2C%0A%20%20%20%20).properties(width%3D400)%0A%20%20%20%20return%20benchmark_df%2C%20benchmark_plot%0A%0A%0A%40app.cell(hide_code%3DTrue)%0Adef%20_()%3A%0A%20%20%20%20import%20asyncio%0A%20%20%20%20import%20timeit%0A%20%20%20%20from%20typing%20import%20Iterable%0A%0A%20%20%20%20import%20altair%20as%20alt%0A%20%20%20%20import%20httpx%0A%20%20%20%20import%20marimo%20as%20mo%0A%20%20%20%20import%20nest_asyncio%0A%20%20%20%20import%20numba%0A%20%20%20%20import%20numpy%20as%20np%0A%20%20%20%20from%20bs4%20import%20BeautifulSoup%0A%0A%20%20%20%20import%20polars%20as%20pl%0A%0A%20%20%20%20%23%20Fixes%20RuntimeError%3A%20asyncio.run()%20cannot%20be%20called%20from%20a%20running%20event%20loop%0A%20%20%20%20nest_asyncio.apply()%0A%20%20%20%20return%20(%0A%20%20%20%20%20%20%20%20BeautifulSoup%2C%0A%20%20%20%20%20%20%20%20Iterable%2C%0A%20%20%20%20%20%20%20%20alt%2C%0A%20%20%20%20%20%20%20%20asyncio%2C%0A%20%20%20%20%20%20%20%20httpx%2C%0A%20%20%20%20%20%20%20%20mo%2C%0A%20%20%20%20%20%20%20%20nest_asyncio%2C%0A%20%20%20%20%20%20%20%20np%2C%0A%20%20%20%20%20%20%20%20numba%2C%0A%20%20%20%20%20%20%20%20pl%2C%0A%20%20%20%20%20%20%20%20timeit%2C%0A%20%20%20%20)%0A%0A%0Aif%20__name__%20%3D%3D%20%22__main__%22%3A%0A%20%20%20%20app.run()%0A</marimo-code></head>
  <body>
    <div id="root"></div>
  </body>
</html>