Spaces:
Running
Running
<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", "theme": "light", "code_editor_font_size": 14, "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>12 aggregations</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 ; | |
} | |
#filename-input { | |
display: none ; | |
} | |
</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%0Adef%20_()%3A%0A%20%20%20%20import%20marimo%20as%20mo%0A%20%20%20%20return%20(mo%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%20Aggregations%0A%20%20%20%20%20%20%20%20_By%20%5BJoram%20Mutenge%5D(https%3A%2F%2Fwww.udemy.com%2Fuser%2Fjoram-mutenge%2F)._%0A%0A%20%20%20%20%20%20%20%20In%20this%20notebook%2C%20you'll%20learn%20how%20to%20perform%20different%20types%20of%20aggregations%20in%20Polars%2C%20including%20grouping%20by%20categories%20and%20time.%20We'll%20analyze%20sales%20data%20from%20a%20clothing%20store%2C%20focusing%20on%20three%20product%20categories%3A%20hats%2C%20socks%2C%20and%20sweaters.%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%0Adef%20_()%3A%0A%20%20%20%20import%20polars%20as%20pl%0A%0A%20%20%20%20df%20%3D%20(pl.read_csv('https%3A%2F%2Fraw.githubusercontent.com%2Fjorammutenge%2Flearn-rust%2Frefs%2Fheads%2Fmain%2Fsample_sales.csv'%2C%20try_parse_dates%3DTrue)%0A%20%20%20%20%20%20%20%20%20%20.rename(lambda%20col%3A%20col.replace('%20'%2C'_').lower())%0A%20%20%20%20%20%20%20%20%20)%0A%20%20%20%20df%0A%20%20%20%20return%20df%2C%20pl%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%20Grouping%20by%20category%0A%20%20%20%20%20%20%20%20%23%23%23%20With%20single%20category%0A%20%20%20%20%20%20%20%20Let's%20find%20out%20how%20many%20of%20each%20product%20category%20we%20sold.%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%0Adef%20_(df%2C%20pl)%3A%0A%20%20%20%20(df%0A%20%20%20%20%20.group_by('category')%0A%20%20%20%20%20.agg(pl.sum('quantity'))%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%20It%20looks%20like%20we%20sold%20more%20sweaters.%20Maybe%20this%20was%20a%20winter%20season.%0A%0A%20%20%20%20%20%20%20%20Let's%20add%20another%20aggregate%20to%20see%20how%20much%20was%20spent%20on%20the%20total%20units%20for%20each%20product.%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%0Adef%20_(df%2C%20pl)%3A%0A%20%20%20%20(df%0A%20%20%20%20%20.group_by('category')%0A%20%20%20%20%20.agg(pl.sum('quantity')%2C%0A%20%20%20%20%20%20%20%20%20%20pl.sum('ext_price'))%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%22We%20could%20also%20write%20aggregate%20code%20for%20the%20two%20columns%20as%20a%20single%20line.%22%22%22)%0A%20%20%20%20return%0A%0A%0A%40app.cell%0Adef%20_(df%2C%20pl)%3A%0A%20%20%20%20(df%0A%20%20%20%20%20.group_by('category')%0A%20%20%20%20%20.agg(pl.sum('quantity'%2C'ext_price'))%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%22Actually%2C%20the%20way%20we've%20been%20writing%20the%20aggregate%20lines%20is%20syntactic%20sugar.%20Here's%20a%20longer%20way%20of%20doing%20it%20as%20shown%20in%20the%20%5BPolars%20documentation%5D(https%3A%2F%2Fdocs.pola.rs%2Fapi%2Fpython%2Fstable%2Freference%2Fdataframe%2Fapi%2Fpolars.dataframe.group_by.GroupBy.agg.html).%22%22%22)%0A%20%20%20%20return%0A%0A%0A%40app.cell%0Adef%20_(df%2C%20pl)%3A%0A%20%20%20%20(df%0A%20%20%20%20%20.group_by('category')%0A%20%20%20%20%20.agg(pl.col('quantity').sum()%2C%0A%20%20%20%20%20%20%20%20%20%20pl.col('ext_price').sum())%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%23%20With%20multiple%20categories%0A%20%20%20%20%20%20%20%20We%20can%20also%20group%20by%20multiple%20categories.%20Let's%20find%20out%20how%20many%20items%20we%20sold%20in%20each%20product%20category%20for%20each%20SKU.%20This%20more%20detailed%20aggregation%20will%20produce%20more%20rows%20than%20the%20previous%20DataFrame.%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%0Adef%20_(df%2C%20pl)%3A%0A%20%20%20%20(df%0A%20%20%20%20%20.group_by('category'%2C'sku')%0A%20%20%20%20%20.agg(pl.sum('quantity'))%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%20Aggregations%20when%20grouping%20data%20are%20not%20limited%20to%20sums.%20You%20can%20also%20use%20functions%20like%20%5B%60max%60%2C%20%60min%60%2C%20%60median%60%2C%20%60first%60%2C%20and%20%60last%60%5D(https%3A%2F%2Fdocs.pola.rs%2Fuser-guide%2Fexpressions%2Faggregation%2F%23basic-aggregations).%20%20%0A%0A%20%20%20%20%20%20%20%20Let's%20find%20the%20largest%20sale%20quantity%20for%20each%20product%20category.%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%0Adef%20_(df%2C%20pl)%3A%0A%20%20%20%20(df%0A%20%20%20%20%20.group_by('category')%0A%20%20%20%20%20.agg(pl.max('quantity'))%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%20make%20the%20aggregation%20more%20interesting.%20We'll%20identify%20the%20first%20customer%20to%20purchase%20each%20item%2C%20along%20with%20the%20quantity%20they%20bought%20and%20the%20amount%20they%20spent.%0A%0A%20%20%20%20%20%20%20%20**Note%3A**%20To%20make%20this%20work%2C%20we'll%20have%20to%20sort%20the%20date%20from%20earliest%20to%20latest.%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%0Adef%20_(df%2C%20pl)%3A%0A%20%20%20%20(df%0A%20%20%20%20%20.sort('date')%0A%20%20%20%20%20.group_by('category')%0A%20%20%20%20%20.agg(pl.first('account_name'%2C'quantity'%2C'ext_price'))%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%20Grouping%20by%20time%0A%20%20%20%20%20%20%20%20Since%20%60datetime%60%20is%20a%20special%20data%20type%20in%20Polars%2C%20we%20can%20perform%20various%20group-by%20aggregations%20on%20it.%20%20%0A%0A%20%20%20%20%20%20%20%20Our%20dataset%20spans%20a%20two-year%20period.%20Let's%20calculate%20the%20total%20dollar%20sales%20for%20each%20year.%20We'll%20do%20it%20the%20naive%20way%20first%20so%20you%20can%20appreciate%20grouping%20with%20time.%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%0Adef%20_(df%2C%20pl)%3A%0A%20%20%20%20(df%0A%20%20%20%20%20.with_columns(year%3Dpl.col('date').dt.year())%0A%20%20%20%20%20.group_by('year')%0A%20%20%20%20%20.agg(pl.sum('ext_price').round(2))%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%20We%20had%20more%20sales%20in%202014.%0A%0A%20%20%20%20%20%20%20%20Now%20let's%20perform%20the%20above%20operation%20by%20groupin%20with%20time.%20This%20requires%20sorting%20the%20dataframe%20first.%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%0Adef%20_(df%2C%20pl)%3A%0A%20%20%20%20(df%0A%20%20%20%20%20.sort('date')%0A%20%20%20%20%20.group_by_dynamic('date'%2C%20every%3D'1y')%0A%20%20%20%20%20.agg(pl.sum('ext_price'))%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%20The%20beauty%20of%20grouping%20with%20time%20is%20that%20it%20allows%20us%20to%20resample%20the%20data%20by%20selecting%20whatever%20time%20interval%20we%20want.%0A%0A%20%20%20%20%20%20%20%20Let's%20find%20out%20what%20the%20quarterly%20sales%20were%20for%202014%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%0Adef%20_(df%2C%20pl)%3A%0A%20%20%20%20(df%0A%20%20%20%20%20.filter(pl.col('date').dt.year()%20%3D%3D%202014)%0A%20%20%20%20%20.sort('date')%0A%20%20%20%20%20.group_by_dynamic('date'%2C%20every%3D'1q')%0A%20%20%20%20%20.agg(pl.sum('ext_price'))%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%20Here's%20an%20interesting%20question%20we%20can%20answer%20that%20takes%20advantage%20of%20grouping%20by%20time.%0A%0A%20%20%20%20%20%20%20%20Let's%20find%20the%20hour%20of%20the%20day%20where%20we%20had%20the%20most%20sales%20in%20dollars.%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%0Adef%20_(df%2C%20pl)%3A%0A%20%20%20%20(df%0A%20%20%20%20%20.sort('date')%0A%20%20%20%20%20.group_by_dynamic('date'%2C%20every%3D'1h')%0A%20%20%20%20%20.agg(pl.max('ext_price'))%0A%20%20%20%20%20.filter(pl.col('ext_price')%20%3D%3D%20pl.col('ext_price').max())%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%22Just%20for%20fun%2C%20let's%20find%20the%20median%20number%20of%20items%20sold%20in%20each%20SKU%20and%20the%20total%20dollar%20amount%20in%20each%20SKU%20every%20six%20days.%22%22%22)%0A%20%20%20%20return%0A%0A%0A%40app.cell%0Adef%20_(df%2C%20pl)%3A%0A%20%20%20%20(df%0A%20%20%20%20%20.sort('date')%0A%20%20%20%20%20.group_by_dynamic('date'%2C%20every%3D'6d')%0A%20%20%20%20%20.agg(pl.first('sku')%2C%0A%20%20%20%20%20%20%20%20%20%20pl.median('quantity')%2C%0A%20%20%20%20%20%20%20%20%20%20pl.sum('ext_price'))%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%22Let's%20rename%20the%20columns%20to%20clearly%20indicate%20the%20type%20of%20aggregation%20performed.%20This%20will%20help%20us%20identify%20the%20aggregation%20method%20used%20on%20a%20column%20without%20needing%20to%20check%20the%20code.%22%22%22)%0A%20%20%20%20return%0A%0A%0A%40app.cell%0Adef%20_(df%2C%20pl)%3A%0A%20%20%20%20(df%0A%20%20%20%20%20.sort('date')%0A%20%20%20%20%20.group_by_dynamic('date'%2C%20every%3D'6d')%0A%20%20%20%20%20.agg(pl.first('sku')%2C%0A%20%20%20%20%20%20%20%20%20%20pl.median('quantity').alias('median_qty')%2C%0A%20%20%20%20%20%20%20%20%20%20pl.sum('ext_price').alias('total_dollars'))%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%20Grouping%20with%20over%0A%0A%20%20%20%20%20%20%20%20Sometimes%2C%20we%20may%20want%20to%20perform%20an%20aggregation%20but%20also%20keep%20all%20the%20columns%20and%20rows%20of%20the%20dataframe.%0A%0A%20%20%20%20%20%20%20%20Let's%20assign%20a%20value%20to%20indicate%20the%20number%20of%20times%20each%20customer%20visited%20and%20bought%20something.%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%0Adef%20_(df%2C%20pl)%3A%0A%20%20%20%20(df%0A%20%20%20%20%20.with_columns(buy_freq%3Dpl.col('account_name').len().over('account_name'))%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%22Finally%2C%20let's%20determine%20which%20customers%20visited%20the%20store%20the%20most%20and%20bought%20something.%22%22%22)%0A%20%20%20%20return%0A%0A%0A%40app.cell%0Adef%20_(df%2C%20pl)%3A%0A%20%20%20%20(df%0A%20%20%20%20%20.with_columns(buy_freq%3Dpl.col('account_name').len().over('account_name'))%0A%20%20%20%20%20.filter(pl.col('buy_freq')%20%3D%3D%20pl.col('buy_freq').max())%0A%20%20%20%20%20.select('account_name'%2C'buy_freq')%0A%20%20%20%20%20.unique()%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%22There's%20more%20you%20can%20do%20with%20aggregations%20in%20Polars%20such%20as%20%5Bsorting%20with%20aggregations%5D(https%3A%2F%2Fdocs.pola.rs%2Fuser-guide%2Fexpressions%2Faggregation%2F%23sorting).%20We%20hope%20that%20in%20this%20notebook%2C%20we've%20armed%20you%20with%20the%20tools%20to%20get%20started.%22%22%22)%0A%20%20%20%20return%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> | |