Loading the chapter…
%23%20%2F%2F%2F%20script%0A%23%20requires-python%20%3D%20%22%3E%3D3.12%2C%3C3.15%22%0A%23%20dependencies%20%3D%20%5B%0A%23%20%20%20%20%20%22marimo%5Bsql%5D%22%2C%0A%23%20%20%20%20%20%22duckdb%22%2C%0A%23%20%20%20%20%20%22polars%22%2C%0A%23%20%20%20%20%20%22pyarrow%22%2C%0A%23%20%20%20%20%20%22altair%22%2C%0A%23%20%20%20%20%20%22matplotlib%22%2C%0A%23%20%20%20%20%20%22pandas%3E%3D3%2C%3C4%22%2C%0A%23%20%5D%0A%23%20%2F%2F%2F%0A%22%22%22Python%20Handbook%2C%20Chapter%20C%3A%20Data.%0A%22%22%22%0A%0Aimport%20marimo%0A%0A__generated_with%20%3D%20%220.25.2%22%0Aapp%20%3D%20marimo.App(width%3D%22medium%22%2C%20sql_output%3D%22polars%22)%0A%0A%0A%40app.cell%0Adef%20_()%3A%0A%20%20%20%20from%20datetime%20import%20date%2C%20datetime%0A%20%20%20%20from%20io%20import%20BytesIO%0A%20%20%20%20from%20pathlib%20import%20Path%0A%20%20%20%20from%20urllib.request%20import%20urlopen%0A%0A%20%20%20%20import%20altair%20as%20alt%0A%20%20%20%20import%20marimo%20as%20mo%0A%20%20%20%20import%20polars%20as%20pl%0A%0A%20%20%20%20return%20BytesIO%2C%20Path%2C%20alt%2C%20date%2C%20datetime%2C%20mo%2C%20pl%2C%20urlopen%0A%0A%0A%40app.cell(hide_code%3DTrue)%0Adef%20_(mo)%3A%0A%20%20%20%20mo.md(r%22%22%22%0A%20%20%20%20%23%20Data%0A%0A%20%20%20%20Chapter%20C%20of%20the%20Python%20Handbook%2C%20in%2011%20sections%2C%20each%20one%20concept.%20Read%20this%20chapter%0A%20%20%20%20and%20you%20can%20open%20a%20file%20somebody%20else%20made%2C%20work%20out%20what%20is%20wrong%20with%20it%2C%20answer%20a%0A%20%20%20%20question%20from%20it%2C%20and%20draw%20the%20answer.%0A%0A%20%20%20%20Chapters%20A%20and%20B%20ran%20on%20numbers%20typed%20into%20the%20cell.%20Nothing%20could%20go%20wrong%20before%0A%20%20%20%20the%20lesson%20started.%20This%20chapter%20runs%20on%20a%20real%20file%2C%20because%20the%20subject%20of%20the%0A%20%20%20%20chapter%20**is**%20the%20file%3A%20what%20type%20each%20column%20came%20back%20as%2C%20what%20is%20missing%20from%20it%2C%0A%20%20%20%20and%20which%20of%20your%20assumptions%20about%20it%20are%20wrong.%0A%0A%20%20%20%20Every%20table%20in%20this%20chapter%20is%20a%20**polars**%20DataFrame.%20Section%2030%20shows%20the%20other%0A%20%20%20%20library%20you%20will%20be%20handed%20tables%20in%2C%20pandas%2C%20and%20how%20to%20tell%20the%20two%20apart.%0A%0A%20%20%20%20You%20can%20read%20this%20straight%20through%2C%20or%20open%20it%20when%20you%20need%20one%20thing.%20Every%20section%0A%20%20%20%20ends%20with%20questions%20that%20have%20no%20answers%20written%20down%2C%20because%20those%20are%20the%20ones%0A%20%20%20%20asked%20out%20loud%20in%20class.%0A%0A%20%20%20%20To%20run%20the%20code%20yourself%2C%20download%20the%20%60.py%60%20into%20your%20project%20and%20open%20it%20with%0A%20%20%20%20%60uv%20run%20marimo%20edit%60.%0A%0A%20%20%20%20%23%23%23%20The%20data%0A%0A%20%20%20%20**Online%20Retail**%2C%20every%20transaction%20of%20a%20UK%20online%20giftware%20retailer%20between%0A%20%20%20%201%20December%202010%20and%209%20December%202011.%20It%20holds%20541%2C909%20rows%20in%20eight%20columns.%0A%0A%20%20%20%20%3E%20Chen%2C%20D.%20(2015).%20*Online%20Retail*%20%5BDataset%5D.%20UCI%20Machine%20Learning%20Repository.%0A%20%20%20%20%3E%20%3Chttps%3A%2F%2Fdoi.org%2F10.24432%2FC5BW33%3E.%20Licensed%20CC%20BY%204.0.%0A%0A%20%20%20%20Sections%2023%20and%2027%20also%20use%20**Northwind**%2C%20the%20course%20warehouse%2C%20because%20a%20join%0A%20%20%20%20needs%20two%20tables%20and%20Northwind%20has%20keys%20that%20resolve.%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%20mo.md(r%22%22%22%0A%20%20%20%20---%0A%20%20%20%20%23%23%2020.%20Files%20and%20paths%0A%0A%20%20%20%20A%20path%20is%20the%20address%20of%20a%20file.%20Before%20polars%20can%20read%20anything%20you%20have%20to%20hand%20it%0A%20%20%20%20one%2C%20and%20the%20most%20common%20failure%20in%20the%20first%20week%20of%20real%20data%20is%20that%20the%20address%0A%20%20%20%20was%20right%20on%20your%20machine%20and%20wrong%20everywhere%20else.%0A%0A%20%20%20%20%F0%9F%93%96%20**Used%20in%3A**%20From%20a%20CSV%20file%20to%20a%20dataframe%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%20mo.md(r%22%22%22%0A%20%20%20%20%23%23%23%20A%20path%20is%20an%20object%20with%20parts%0A%0A%20%20%20%20%60Path%60%20comes%20from%20the%20standard%20library.%20Build%20one%20with%20%60%2F%60%20between%20the%20pieces%2C%20and%20it%0A%20%20%20%20assembles%20the%20separator%20your%20operating%20system%20uses.%20The%20parts%20have%20names.%0A%20%20%20%20%22%22%22)%0A%20%20%20%20return%0A%0A%0A%40app.cell%0Adef%20_(Path)%3A%0A%20%20%20%20example_path%20%3D%20Path(%22data%22)%20%2F%20%22online-retail%22%20%2F%20%22online-retail.parquet%22%0A%0A%20%20%20%20example_path.name%2C%20example_path.stem%2C%20example_path.suffix%2C%20example_path.parent%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%22%0A%20%20%20%20%F0%9F%91%89%20**Try%20it%3A**%20build%20a%20path%20to%20%60reports%2F2011%2Fnovember.csv%60%20the%20same%20way%2C%20then%20ask%20it%0A%20%20%20%20for%20%60.parent.parent%60.%20Write%20the%20answer%20down%20before%20you%20run%20it.%0A%0A%20%20%20%20**Why%20a%20%60Path%60%20and%20not%20a%20string.**%20A%20string%20only%20holds%20the%20path.%20A%20%60Path%60%20also%20tells%20you%0A%20%20%20%20its%20folder%20with%20%60.parent%60%2C%20its%20file%20name%20with%20%60.name%60%2C%20and%20whether%20the%20file%20is%20there%0A%20%20%20%20with%20%60.exists()%60%2C%20which%20is%20the%20first%20thing%20to%20ask%20when%20a%20read%20fails.%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%20mo.md(r%22%22%22%0A%20%20%20%20%23%23%23%20A%20relative%20path%20is%20relative%20to%20where%20you%20started%0A%0A%20%20%20%20Every%20running%20program%20has%20a%20**current%20directory**%2C%20which%20is%20the%20default%20for%20anything%0A%20%20%20%20that%20does%20not%20say%20otherwise.%20%60Path(%22sales.csv%22)%60%20means%20*sales.csv%20in%20the%20current%0A%20%20%20%20directory*%2C%20and%20the%20current%20directory%20is%20where%20the%20program%20was%20launched%20from%2C%20which%0A%20%20%20%20is%20not%20necessarily%20where%20your%20file%20sits.%0A%20%20%20%20%22%22%22)%0A%20%20%20%20return%0A%0A%0A%40app.cell%0Adef%20_(Path)%3A%0A%20%20%20%20Path.cwd()%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%22%0A%20%20%20%20That%20is%20the%20folder%20this%20notebook%20was%20started%20from.%20Start%20it%20from%20somewhere%20else%20and%0A%20%20%20%20the%20same%20relative%20path%20points%20somewhere%20else%2C%20with%20no%20warning.%0A%0A%20%20%20%20%60%60%60python%0A%20%20%20%20import%20polars%20as%20pl%0A%20%20%20%20retail%20%3D%20pl.read_parquet(%22online-retail.parquet%22)%0A%20%20%20%20%60%60%60%0A%0A%20%20%20%20%60%60%60%0A%20%20%20%20FileNotFoundError%3A%20No%20such%20file%20or%20directory%20(os%20error%202)%3A%20online-retail.parquet%0A%20%20%20%20%60%60%60%0A%0A%20%20%20%20%F0%9F%91%89%20**Try%20it%3A**%20print%20%60Path.cwd()%60%2C%20then%20run%20the%20same%20line%20from%20a%20terminal%20opened%20in%20a%0A%20%20%20%20different%20folder.%20The%20code%20did%20not%20change%20and%20the%20answer%20did.%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%20mo.md(r%22%22%22%0A%20%20%20%20%23%23%23%20Anchor%20the%20path%20to%20the%20notebook's%20folder%0A%0A%20%20%20%20The%20fix%20is%20to%20stop%20asking%20*where%20was%20I%20started*%20and%20start%20asking%20*where%20am%20I*.%20In%20a%0A%20%20%20%20marimo%20notebook%20that%20is%20%60mo.notebook_dir()%60.%20In%20a%20plain%20%60.py%60%20script%20it%20is%0A%20%20%20%20%60Path(__file__).parent%60.%20Either%20way%20you%20get%20the%20folder%20holding%20the%20code%2C%20and%20you%20walk%0A%20%20%20%20up%20from%20there%20with%20%60.parent%60%20until%20you%20reach%20the%20top%20of%20the%20project.%0A%0A%20%20%20%20This%20notebook%20sits%20in%20%60notebooks%2Fhandbooks%2Fpython%2F%60%2C%20so%20the%20top%20of%20the%20project%20is%0A%20%20%20%20three%20levels%20up.%0A%20%20%20%20%22%22%22)%0A%20%20%20%20return%0A%0A%0A%40app.cell%0Adef%20_(mo)%3A%0A%20%20%20%20here%20%3D%20mo.notebook_dir()%0A%20%20%20%20repo_root%20%3D%20here.parent.parent.parent%0A%20%20%20%20retail_path%20%3D%20repo_root%20%2F%20%22data%22%20%2F%20%22online-retail%22%20%2F%20%22online-retail.parquet%22%0A%0A%20%20%20%20here.name%2C%20repo_root.name%2C%20retail_path.exists()%0A%20%20%20%20return%20repo_root%2C%20retail_path%0A%0A%0A%40app.cell(hide_code%3DTrue)%0Adef%20_(mo)%3A%0A%20%20%20%20mo.md(r%22%22%22%0A%20%20%20%20%F0%9F%91%89%20**Try%20it%3A**%20print%20%60repo_root%60%20on%20its%20own%2C%20then%20%60sorted(p.name%20for%20p%20in%0A%20%20%20%20repo_root.iterdir())%5B%3A5%5D%60.%20You%20are%20reading%20the%20project%20from%20the%20outside.%0A%0A%20%20%20%20**The%20trap.**%20%60Path.cwd()%60%20looks%20like%20it%20does%20the%20same%20job%20and%20does%20not.%20It%20answers%20a%0A%20%20%20%20question%20about%20the%20terminal%2C%20and%20%60mo.notebook_dir()%60%20answers%20a%20question%20about%20the%0A%20%20%20%20file.%20Only%20the%20second%20one%20is%20still%20true%20tomorrow%20when%20somebody%20runs%20your%20notebook%0A%20%20%20%20from%20their%20home%20folder.%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%20mo.md(r%22%22%22%0A%20%20%20%20%23%23%23%20Ask%20a%20path%20whether%20it%20is%20there%20before%20you%20read%20it%0A%0A%20%20%20%20%60.exists()%60%20returns%20%60True%60%20or%20%60False%60%20and%20raises%20nothing.%20%60.stat().st_size%60%20gives%20the%0A%20%20%20%20size%20in%20bytes.%20%60glob%60%20lists%20what%20a%20folder%20holds%2C%20which%20is%20how%20you%20find%20out%20that%20the%0A%20%20%20%20file%20is%20called%20%60online-retail.parquet%60%20and%20not%20%60online_retail.parquet%60.%0A%20%20%20%20%22%22%22)%0A%20%20%20%20return%0A%0A%0A%40app.cell%0Adef%20_(repo_root%2C%20retail_path)%3A%0A%20%20%20%20_folder%20%3D%20repo_root%20%2F%20%22data%22%20%2F%20%22online-retail%22%0A%20%20%20%20_names%20%3D%20sorted(p.name%20for%20p%20in%20_folder.glob(%22*%22))%20if%20_folder.exists()%20else%20%5B%5D%0A%20%20%20%20_size%20%3D%20(f%22%7Bretail_path.stat().st_size%20%2F%201_000_000%3A.1f%7D%20MB%22%0A%20%20%20%20%20%20%20%20%20%20%20%20%20if%20retail_path.exists()%20else%20%22not%20here%22)%0A%0A%20%20%20%20_names%2C%20_size%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%22%0A%20%20%20%20The%20file%20is%203.6%20MB%20and%20holds%20541%2C909%20rows.%20The%20same%20data%20as%20a%20CSV%20is%20about%2050%20MB.%0A%0A%20%20%20%20If%20you%20downloaded%20this%20chapter%20as%20a%20single%20%60.py%60%20rather%20than%20the%20whole%20project%2C%20the%0A%20%20%20%20folder%20is%20empty%20and%20the%20size%20says%20%60not%20here%60.%20That%20is%20the%20correct%20answer%20and%20the%0A%20%20%20%20reason%20this%20cell%20asks.%20Every%20cell%20that%20needs%20the%20data%20falls%20back%20to%20fetching%20it%20over%0A%20%20%20%20HTTP%2C%20so%20the%20chapter%20runs%20either%20way.%0A%0A%20%20%20%20%F0%9F%91%89%20**Try%20it%3A**%20point%20a%20path%20at%20a%20file%20that%20does%20not%20exist%20and%20call%20%60.exists()%60%20on%20it.%0A%20%20%20%20Then%20call%20%60.stat()%60%20on%20the%20same%20path%20and%20read%20what%20comes%20back.%0A%0A%20%20%20%20%23%23%23%20%E2%9C%94%20Check%20your%20understanding%0A%0A%20%20%20%201.%20Your%20script%20reads%20%60data%2Fsales.csv%60%20and%20works.%20A%20colleague%20runs%20it%20and%20gets%0A%20%20%20%20%20%20%20%60FileNotFoundError%60.%20Name%20two%20things%20that%20could%20differ%20between%20your%20machine%20and%0A%20%20%20%20%20%20%20theirs.%0A%20%20%20%202.%20What%20question%20does%20%60Path.cwd()%60%20answer%2C%20and%20what%20question%20does%0A%20%20%20%20%20%20%20%60Path(__file__).parent%60%20answer%3F%0A%20%20%20%203.%20You%20are%20given%20a%20path%20and%20told%20the%20file%20might%20not%20be%20there.%20What%20do%20you%20call%20before%0A%20%20%20%20%20%20%20you%20read%20it%2C%20and%20what%20does%20it%20return%3F%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%20mo.md(r%22%22%22%0A%20%20%20%20---%0A%20%20%20%20%23%23%2021.%20Parquet%2C%20CSV%20and%20JSON%0A%0A%20%20%20%20Data%20arrives%20in%20three%20shapes%20in%20this%20course.%20**Parquet**%20keeps%20its%20types%2C%20**CSV**%0A%20%20%20%20throws%20them%20away%2C%20and%20**JSON**%20nests.%20Which%20one%20you%20were%20handed%20decides%20how%20much%20work%0A%20%20%20%20happens%20before%20the%20first%20calculation.%0A%0A%20%20%20%20%F0%9F%93%96%20**Used%20in%3A**%20From%20a%20CSV%20file%20to%20a%20dataframe%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%20mo.md(r%22%22%22%0A%20%20%20%20%23%23%23%20%60pl.read_parquet%60%20gives%20the%20types%20back%20with%20the%20rows%0A%0A%20%20%20%20A%20parquet%20file%20stores%20what%20each%20column%20is%2C%20so%20a%20date%20arrives%20as%20a%20date%20and%20a%20number%0A%20%20%20%20arrives%20as%20a%20number.%20One%20line%2C%20and%20nothing%20to%20declare.%0A%0A%20%20%20%20The%20cell%20below%20falls%20back%20to%20the%20published%20copy%20over%20HTTPS%20when%20the%20repository%20copy%0A%20%20%20%20is%20missing%2C%20so%20this%20notebook%20runs%20after%20a%20download%20of%20the%20single%20%60.py%60%20file.%0A%20%20%20%20%22%22%22)%0A%20%20%20%20return%0A%0A%0A%40app.cell%0Adef%20_(BytesIO%2C%20pl%2C%20retail_path%2C%20urlopen)%3A%0A%20%20%20%20if%20retail_path.exists()%3A%0A%20%20%20%20%20%20%20%20retail%20%3D%20pl.read_parquet(retail_path)%0A%20%20%20%20else%3A%0A%20%20%20%20%20%20%20%20_url%20%3D%20%22https%3A%2F%2Foim7510.github.io%2Fdata%2Fonline-retail%2Fonline-retail.parquet%22%0A%20%20%20%20%20%20%20%20retail%20%3D%20pl.read_parquet(BytesIO(urlopen(_url).read()))%0A%0A%20%20%20%20retail.shape%0A%20%20%20%20return%20(retail%2C)%0A%0A%0A%40app.cell%0Adef%20_(retail)%3A%0A%20%20%20%20retail.schema%0A%20%20%20%20return%0A%0A%0A%40app.cell(hide_code%3DTrue)%0Adef%20_(mo%2C%20retail)%3A%0A%20%20%20%20mo.md(f%22%22%22%0A%20%20%20%20Eight%20columns%2C%20%7Bretail.height%3A%2C%7D%20rows.%20%60InvoiceDate%60%20came%20back%20as%0A%20%20%20%20%60%7Bretail.schema%5B%22InvoiceDate%22%5D%7D%60%2C%20which%20means%20the%20month%20is%20already%20available%20and%0A%20%20%20%20nothing%20has%20to%20be%20parsed.%20Hold%20on%20to%20that%2C%20because%20the%20next%20cell%20takes%20it%20away.%0A%0A%20%20%20%20%F0%9F%91%89%20**Try%20it%3A**%20run%20%60retail.head()%60%20and%20then%20%60retail.tail(3)%60.%20With%0A%20%20%20%20%7Bretail.height%3A%2C%7D%20rows%2C%20%60.head()%60%20is%20an%20instruction%20and%20not%20a%20suggestion%3A%20printing%20the%0A%20%20%20%20whole%20frame%20gives%20you%20a%20screen%20of%20dots%20and%20a%20slow%20page.%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%20mo.md(r%22%22%22%0A%20%20%20%20%23%23%23%20A%20CSV%20has%20no%20types%0A%0A%20%20%20%20Write%20part%20of%20the%20frame%20out%20as%20a%20CSV%20and%20read%20it%20straight%20back.%20Nothing%20was%20lost%20on%0A%20%20%20%20the%20way%20to%20the%20disk%2C%20and%20something%20is%20lost%20on%20the%20way%20back.%0A%20%20%20%20%22%22%22)%0A%20%20%20%20return%0A%0A%0A%40app.cell%0Adef%20_(Path%2C%20pl%2C%20retail)%3A%0A%20%20%20%20import%20tempfile%0A%0A%20%20%20%20work_dir%20%3D%20Path(tempfile.mkdtemp())%0A%20%20%20%20france_csv_path%20%3D%20work_dir%20%2F%20%22france-2011.csv%22%0A%0A%20%20%20%20retail.filter(pl.col(%22Country%22)%20%3D%3D%20%22France%22).write_csv(france_csv_path)%0A%20%20%20%20round(france_csv_path.stat().st_size%20%2F%201000)%2C%20france_csv_path.name%0A%20%20%20%20return%20france_csv_path%2C%20work_dir%0A%0A%0A%40app.cell(hide_code%3DTrue)%0Adef%20_(mo)%3A%0A%20%20%20%20mo.md(r%22%22%22%0A%20%20%20%20Read%20it%20back%20with%20no%20arguments%20and%20polars%20stops%3A%0A%0A%20%20%20%20%60%60%60python%0A%20%20%20%20pl.read_csv(france_csv_path)%0A%20%20%20%20%60%60%60%0A%0A%20%20%20%20%60%60%60%0A%20%20%20%20ComputeError%3A%20could%20not%20parse%20%60C537893%60%20as%20dtype%20%60i64%60%20at%20column%20'InvoiceNo'%20(column%20number%201)%0A%20%20%20%20%60%60%60%0A%0A%20%20%20%20A%20CSV%20carries%20no%20types%2C%20so%20polars%20reads%20the%20first%20100%20rows%20and%20guesses%20one%20for%20each%0A%20%20%20%20column.%20Every%20%60InvoiceNo%60%20in%20those%20rows%20is%20six%20digits%2C%20so%20it%20guessed%20a%20whole%20number.%0A%20%20%20%20Row%20179%20is%20%60C537893%60%2C%20a%20cancelled%20invoice%2C%20and%20polars%20stops%20there.%20Your%20copy%20of%20polars%20may%20name%20a%20later%20invoice%20starting%20with%20%60C%60%3B%20the%20cause%20is%20the%20same.%20%60StockCode%60%20got%0A%20%20%20%20through%20only%20because%20%60POST%60%20turns%20up%20in%20row%2020%2C%20inside%20the%20rows%20polars%20looked%20at.%0A%0A%20%20%20%20The%20error%20lists%20the%20fixes%2C%20and%20the%20one%20to%20use%20is%20to%20specify%20the%20column's%20type.%20Specify%20it%20for%0A%20%20%20%20both%20code%20columns%2C%20so%20the%20read%20does%20not%20depend%20on%20where%20the%20first%20letter%20happens%20to%0A%20%20%20%20fall.%0A%20%20%20%20%22%22%22)%0A%20%20%20%20return%0A%0A%0A%40app.cell%0Adef%20_(france_csv_path%2C%20pl)%3A%0A%20%20%20%20code_columns%20%3D%20%7B%22InvoiceNo%22%3A%20pl.String%2C%20%22StockCode%22%3A%20pl.String%7D%0A%20%20%20%20france%20%3D%20pl.read_csv(france_csv_path%2C%20schema_overrides%3Dcode_columns)%0A%0A%20%20%20%20france.schema%0A%20%20%20%20return%20code_columns%2C%20france%0A%0A%0A%40app.cell(hide_code%3DTrue)%0Adef%20_(france%2C%20mo%2C%20retail)%3A%0A%20%20%20%20mo.md(f%22%22%22%0A%20%20%20%20%60InvoiceDate%60%20went%20out%20as%20%60%7Bretail.schema%5B%22InvoiceDate%22%5D%7D%60%20and%20came%20back%20as%0A%20%20%20%20%60%7Bfrance.schema%5B%22InvoiceDate%22%5D%7D%60.%20It%20is%20text%20now.%20Nothing%20raised%2C%20the%20column%20still%0A%20%20%20%20reads%20as%20a%20date%20on%20the%20screen%2C%20and%20every%20date%20operation%20on%20it%20will%20fail%20until%0A%20%20%20%20somebody%20parses%20it.%20That%20is%20section%2025.%0A%0A%20%20%20%20%60pl.read_csv%60%20guessed%20the%20numeric%20columns%20correctly%20because%20they%20look%20numeric.%20It%0A%20%20%20%20does%20not%20guess%20dates%20unless%20you%20ask%2C%20and%20you%20would%20not%20want%20it%20to%3A%20guessing%20is%20how%0A%20%20%20%20%6003%2F04%2F2011%60%20becomes%20the%20third%20of%20April%20in%20one%20file%20and%20the%20fourth%20of%20March%20in%0A%20%20%20%20another.%0A%0A%20%20%20%20%F0%9F%91%89%20**Try%20it%3A**%20read%20the%20file%20with%20%60infer_schema_length%3D10000%60%20and%20no%20overrides%2C%20and%0A%20%20%20%20read%20the%20type%20polars%20chose%20for%20%60InvoiceNo%60%20this%20time.%20Then%20delete%20%60work_dir%60%20when%20you%0A%20%20%20%20are%20done%20with%20it.%0A%0A%20%20%20%20**The%20trap.**%20Writing%20to%20a%20path%20that%20already%20holds%20a%20file%20**clears%20the%20old%20file%20and%0A%20%20%20%20starts%20fresh**.%20There%20is%20no%20prompt%20and%20no%20undo.%20Check%20the%20path%20before%20you%20write%2C%20the%0A%20%20%20%20same%20way%20you%20check%20it%20before%20you%20read.%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%20mo.md(r%22%22%22%0A%20%20%20%20%23%23%23%20%60pl.read_csv%60%20has%20the%20arguments%20you%20will%20actually%20need%0A%0A%20%20%20%20Four%20cover%20almost%20everything.%20All%20four%20exist%20because%20somebody%20handed%20somebody%20else%20a%0A%20%20%20%20file%20that%20needed%20them.%0A%0A%20%20%20%20%7C%20argument%20%7C%20what%20it%20does%20%7C%0A%20%20%20%20%7C---%7C---%7C%0A%20%20%20%20%7C%20%60separator%3D%22%3B%22%60%20%7C%20the%20file%20is%20separated%20by%20something%20other%20than%20a%20comma%20%7C%0A%20%20%20%20%7C%20%60encoding%3D%22latin1%22%60%20%7C%20the%20file%20is%20not%20UTF-8%2C%20which%20is%20what%20an%20error%20about%20invalid%20UTF-8%20means%20%7C%0A%20%20%20%20%7C%20%60schema_overrides%3D%7B%22InvoiceNo%22%3A%20pl.String%7D%60%20%7C%20stop%20polars%20guessing%20on%20a%20column%20you%20know%20about%20%7C%0A%20%20%20%20%7C%20%60try_parse_dates%3DTrue%60%20%7C%20turn%20columns%20that%20look%20like%20dates%20into%20real%20dates%20on%20the%20way%20in%20%7C%0A%20%20%20%20%22%22%22)%0A%20%20%20%20return%0A%0A%0A%40app.cell%0Adef%20_(code_columns%2C%20france_csv_path%2C%20pl)%3A%0A%20%20%20%20_typed%20%3D%20pl.read_csv(%0A%20%20%20%20%20%20%20%20france_csv_path%2C%0A%20%20%20%20%20%20%20%20schema_overrides%3Dcode_columns%2C%0A%20%20%20%20%20%20%20%20try_parse_dates%3DTrue%2C%0A%20%20%20%20)%0A%0A%20%20%20%20_typed.schema%5B%22InvoiceDate%22%5D%2C%20_typed.schema%5B%22InvoiceNo%22%5D%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%22%0A%20%20%20%20Same%20file%2C%20same%20one%20function%2C%20and%20the%20date%20is%20a%20date.%20The%20arguments%20are%20the%20whole%0A%20%20%20%20difference.%0A%0A%20%20%20%20%F0%9F%91%89%20**Try%20it%3A**%20read%20the%20file%20a%20third%20time%20with%20%60n_rows%3D10%60.%20On%20a%2050%20MB%20CSV%20that%20is%20how%0A%20%20%20%20you%20look%20before%20you%20commit.%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%20mo.md(r%22%22%22%0A%20%20%20%20%23%23%23%20The%20%60csv%60%20module%20reads%20a%20file%20as%20a%20list%20of%20records%0A%0A%20%20%20%20Python's%20own%20%60csv%60%20module%20reads%20the%20same%20file%20without%20polars.%20%60csv.DictReader%60%20turns%0A%20%20%20%20each%20line%20into%20a%20dictionary%2C%20with%20the%20header's%20names%20as%20keys%2C%20so%20the%20file%20comes%20back%0A%20%20%20%20as%20a%20list%20of%20records%2C%20the%20shape%20of%20Chapter%20B%20and%20of%20an%20API%20reply.%20It%20handles%20the%0A%20%20%20%20commas%20and%20quotes%20inside%20values%20for%20you.%20The%20empty%20%60newline%60%20setting%20in%20%60open()%60%20is%0A%20%20%20%20what%20the%20module%20asks%20for.%0A%20%20%20%20%22%22%22)%0A%20%20%20%20return%0A%0A%0A%40app.cell%0Adef%20_(france_csv_path)%3A%0A%20%20%20%20import%20csv%0A%0A%20%20%20%20with%20open(france_csv_path%2C%20newline%3D%22%22%2C%20encoding%3D%22utf-8%22)%20as%20_file%3A%0A%20%20%20%20%20%20%20%20france_records%20%3D%20list(csv.DictReader(_file))%0A%0A%20%20%20%20len(france_records)%2C%20france_records%5B0%5D%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%22%0A%20%20%20%20Every%20value%20is%20a%20string%2C%20%60Quantity%60%20and%20%60UnitPrice%60%20included.%20The%20module%20guesses%20no%0A%20%20%20%20types%2C%20so%20you%20apply%20%60int()%60%20and%20%60float()%60%20yourself.%20%60csv.DictWriter%60%20goes%20the%20other%0A%20%20%20%20way%2C%20from%20a%20list%20of%20dictionaries%20to%20a%20file%3A%20give%20it%20%60fieldnames%60%2C%20call%0A%20%20%20%20%60.writeheader()%60%2C%20then%20%60.writerows()%60.%0A%0A%20%20%20%20%F0%9F%91%89%20**Try%20it%3A**%20add%20up%20%60Quantity%60%20over%20%60france_records%60%20with%20a%20%60for%60%20loop%2C%20then%20compare%0A%20%20%20%20it%20with%20%60france%5B%22Quantity%22%5D.sum()%60.%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%20mo.md(r%22%22%22%0A%20%20%20%20%23%23%23%20Reading%20and%20writing%20JSON%20with%20%60load%60%20and%20%60loads%60%0A%0A%20%20%20%20A%20CSV%20is%20a%20rectangle.%20JSON%20is%20a%20shape%3A%20dictionaries%20inside%20lists%20inside%20dictionaries%2C%0A%20%20%20%20which%20is%20what%20almost%20every%20API%20hands%20back.%20In%20Python%20it%20becomes%20exactly%20the%0A%20%20%20%20dictionaries%20and%20lists%20of%20Chapter%20B.%0A%0A%20%20%20%20Four%20function%20names%2C%20and%20the%20%60s%60%20is%20the%20only%20difference%20between%20two%20pairs%20of%20them.%0A%0A%20%20%20%20%7C%20%7C%20reads%20or%20writes%20%7C%0A%20%20%20%20%7C---%7C---%7C%0A%20%20%20%20%7C%20%60json.load(f)%60%20%7C%20an%20open%20**file**%20%7C%0A%20%20%20%20%7C%20%60json.loads(text)%60%20%7C%20a%20**string**%20%7C%0A%20%20%20%20%7C%20%60json.dump(obj%2C%20f)%60%20%7C%20to%20an%20open%20**file**%20%7C%0A%20%20%20%20%7C%20%60json.dumps(obj)%60%20%7C%20to%20a%20**string**%20%7C%0A%20%20%20%20%22%22%22)%0A%20%20%20%20return%0A%0A%0A%40app.cell%0Adef%20_(retail%2C%20work_dir)%3A%0A%20%20%20%20import%20json%0A%0A%20%20%20%20summary_path%20%3D%20work_dir%20%2F%20%22summary.json%22%0A%20%20%20%20_summary%20%3D%20%7B%0A%20%20%20%20%20%20%20%20%22rows%22%3A%20retail.height%2C%0A%20%20%20%20%20%20%20%20%22countries%22%3A%20retail%5B%22Country%22%5D.n_unique()%2C%0A%20%20%20%20%20%20%20%20%22first_invoice%22%3A%20str(retail%5B%22InvoiceDate%22%5D.min())%2C%0A%20%20%20%20%7D%0A%0A%20%20%20%20with%20open(summary_path%2C%20%22w%22%2C%20encoding%3D%22utf-8%22)%20as%20_f%3A%0A%20%20%20%20%20%20%20%20json.dump(_summary%2C%20_f%2C%20indent%3D2)%0A%0A%20%20%20%20summary_path.read_text()%0A%20%20%20%20return%20json%2C%20summary_path%0A%0A%0A%40app.cell%0Adef%20_(json%2C%20summary_path)%3A%0A%20%20%20%20with%20open(summary_path%2C%20encoding%3D%22utf-8%22)%20as%20_f%3A%0A%20%20%20%20%20%20%20%20summary_back%20%3D%20json.load(_f)%0A%0A%20%20%20%20summary_back%5B%22rows%22%5D%2C%20summary_back%5B%22countries%22%5D%2C%20type(summary_back)%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%22%0A%20%20%20%20A%20dictionary%20went%20out%20and%20a%20dictionary%20came%20back.%20%60with%20open(...)%60%20closes%20the%20file%0A%20%20%20%20when%20the%20indented%20block%20ends%2C%20whether%20or%20not%20anything%20went%20wrong%20inside%20it.%0A%0A%20%20%20%20%60%60%60python%0A%20%20%20%20json.load('%7B%22rows%22%3A%20541909%7D')%0A%20%20%20%20%60%60%60%0A%0A%20%20%20%20%60%60%60%0A%20%20%20%20AttributeError%3A%20'str'%20object%20has%20no%20attribute%20'read'%0A%20%20%20%20%60%60%60%0A%0A%20%20%20%20%60load%60%20wanted%20a%20file%20and%20got%20a%20string.%20The%20message%20never%20says%20the%20word%20JSON%2C%20which%20is%0A%20%20%20%20why%20this%20one%20costs%20people%2010%20minutes.%0A%0A%20%20%20%20%F0%9F%91%89%20**Try%20it%3A**%20fix%20that%20line%20two%20ways%2C%20once%20with%20%60json.loads%60%20and%20once%20by%20writing%20the%0A%20%20%20%20string%20to%20a%20file%20first.%20Then%20add%20a%20nested%20list%20to%20the%20summary%20dictionary%2C%20dump%20it%2C%20and%0A%20%20%20%20read%20the%20file%20with%20your%20eyes.%0A%0A%20%20%20%20%23%23%23%20%E2%9C%94%20Check%20your%20understanding%0A%0A%20%20%20%201.%20The%20same%20column%20is%20a%20real%20date%20in%20the%20parquet%20file%20and%20text%20in%20the%20CSV%20written%0A%20%20%20%20%20%20%20from%20it.%20What%20did%20the%20CSV%20lose%2C%20and%20why%20can%20it%20not%20keep%20it%3F%0A%20%20%20%202.%20You%20read%20a%20CSV%20and%20polars%20stops%20with%20%60could%20not%20parse%20...%20as%20dtype%20i64%60.%20What%20did%20it%0A%20%20%20%20%20%20%20guess%2C%20how%20did%20it%20guess%2C%20and%20which%20argument%20settles%20it%3F%0A%20%20%20%203.%20%60json.load%60%20and%20%60json.loads%60%20both%20parse%20JSON.%20Which%20one%20takes%20a%20file%2C%20and%20what%20is%0A%20%20%20%20%20%20%20the%20error%20message%20when%20you%20get%20it%20the%20wrong%20way%20round%3F%0A%20%20%20%204.%20You%20open%20a%20file%20with%20%60with%20open(...)%60%20and%20never%20call%20%60.close()%60.%20Is%20the%20file%0A%20%20%20%20%20%20%20closed%3F%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%20mo.md(r%22%22%22%0A%20%20%20%20---%0A%20%20%20%20%23%23%2022.%20%60try%60%20%2F%20%60except%60%2C%20and%20%60assert%60%0A%0A%20%20%20%20Two%20different%20things%20go%20wrong%20with%20data%2C%20and%20they%20need%20opposite%20tools.%20Sometimes%20the%0A%20%20%20%20code%20stops%2C%20and%20%60try%60%20%2F%20%60except%60%20decides%20what%20happens%20next.%20Sometimes%20the%20code%0A%20%20%20%20finishes%20and%20the%20answer%20is%20wrong%2C%20and%20%60assert%60%20is%20how%20you%20make%20that%20one%20stop%20too.%0A%0A%20%20%20%20%F0%9F%93%96%20**Used%20in%3A**%20Signing%20off%20on%20somebody%20else's%20work%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%20mo.md(r%22%22%22%0A%20%20%20%20%23%23%23%20What%20%60try%60%20and%20%60except%60%20each%20do%0A%0A%20%20%20%20Put%20the%20line%20that%20might%20fail%20in%20the%20%60try%60%20block.%20Name%20the%20exception%20you%20expect%20in%20the%0A%20%20%20%20%60except%60%20clause.%20If%20it%20happens%2C%20the%20%60except%60%20block%20runs%20and%20the%20program%20carries%20on.%0A%20%20%20%20%22%22%22)%0A%20%20%20%20return%0A%0A%0A%40app.cell%0Adef%20_(pl%2C%20work_dir)%3A%0A%20%20%20%20_missing%20%3D%20work_dir%20%2F%20%22not-here.csv%22%0A%0A%20%20%20%20try%3A%0A%20%20%20%20%20%20%20%20_orders%20%3D%20pl.read_csv(_missing)%0A%20%20%20%20%20%20%20%20read_result%20%3D%20f%22%7B_orders.height%7D%20rows%22%0A%20%20%20%20except%20FileNotFoundError%3A%0A%20%20%20%20%20%20%20%20read_result%20%3D%20f%22no%20file%20at%20%7B_missing.name%7D%2C%20using%20an%20empty%20result%22%0A%0A%20%20%20%20read_result%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%22%0A%20%20%20%20That%20cell%20ran%20to%20the%20end%20and%20produced%20a%20value.%20Without%20the%20%60try%60%20it%20would%20have%0A%20%20%20%20stopped%2C%20and%20everything%20below%20it%20would%20never%20have%20run.%0A%0A%20%20%20%20%F0%9F%91%89%20**Try%20it%3A**%20add%20an%20%60else%3A%60%20block%2C%20which%20runs%20only%20when%20nothing%20was%20raised%2C%20and%20a%0A%20%20%20%20%60finally%3A%60%20block%2C%20which%20runs%20either%20way.%20Predict%20which%20of%20them%20prints%20when%20the%20file%20is%0A%20%20%20%20missing.%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%20mo.md(r%22%22%22%0A%20%20%20%20%23%23%23%20Catch%20only%20the%20exception%20you%20expected%0A%0A%20%20%20%20A%20bare%20%60except%3A%60%20catches%20everything%2C%20including%20the%20typo%20you%20just%20made%20three%20lines%20up.%0A%20%20%20%20The%20program%20then%20reports%20the%20failure%20you%20predicted%20while%20suffering%20a%20different%20one.%0A%0A%20%20%20%20%60%60%60python%0A%20%20%20%20%23%20Do%20not%20write%20this.%0A%20%20%20%20try%3A%0A%20%20%20%20%20%20%20%20orders%20%3D%20pl.read_csv(path)%0A%20%20%20%20except%3A%0A%20%20%20%20%20%20%20%20print(%22File%20not%20found%22)%0A%20%20%20%20%60%60%60%0A%0A%20%20%20%20%60pl.read_csv%60%20can%20raise%20%60FileNotFoundError%60%2C%20and%20it%20can%20also%20raise%20%60ComputeError%60%0A%20%20%20%20when%20a%20value%20will%20not%20parse%2C%20as%20it%20did%20in%20section%2021%2C%20and%20%60PermissionError%60.%20All%20three%0A%20%20%20%20print%20*File%20not%20found*.%20So%20does%20%60pl.read_csvv%60.%0A%0A%20%20%20%20A%20real%20example%20from%20this%20course's%20own%20archive%3A%20a%20script%20caught%20a%20%60KeyError%60%20and%0A%20%20%20%20printed%20%60JSON%20parsing%20error%60.%20The%20JSON%20parsed%20correctly.%20The%20assumption%20about%20which%0A%20%20%20%20keys%20it%20contained%20was%20wrong%2C%20and%20the%20message%20sent%20everybody%20to%20look%20at%20the%20wrong%0A%20%20%20%20thing.%0A%20%20%20%20%22%22%22)%0A%20%20%20%20return%0A%0A%0A%40app.cell%0Adef%20_(pl%2C%20work_dir)%3A%0A%20%20%20%20try%3A%0A%20%20%20%20%20%20%20%20_orders%20%3D%20pl.read_csv(work_dir%20%2F%20%22also-not-here.csv%22)%0A%20%20%20%20%20%20%20%20typed_result%20%3D%20%22read%20it%22%0A%20%20%20%20except%20FileNotFoundError%20as%20_err%3A%0A%20%20%20%20%20%20%20%20typed_result%20%3D%20f%22%7Btype(_err).__name__%7D%3A%20there%20is%20no%20file%20at%20that%20path%22%0A%20%20%20%20except%20pl.exceptions.ComputeError%3A%0A%20%20%20%20%20%20%20%20typed_result%20%3D%20%22the%20file%20is%20there%20and%20a%20value%20in%20it%20would%20not%20parse%22%0A%0A%20%20%20%20typed_result%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%22%0A%20%20%20%20%60as%20_err%60%20binds%20the%20exception%20to%20a%20name%20so%20the%20handler%20can%20report%20what%20actually%0A%20%20%20%20happened.%20Two%20%60except%60%20clauses%20on%20one%20%60try%60%20is%20normal%2C%20and%20the%20first%20matching%20one%0A%20%20%20%20runs.%0A%0A%20%20%20%20%F0%9F%91%89%20**Try%20it%3A**%20write%20a%20%60try%60%20around%20%60int(%22n%2Fa%22)%60%20and%20catch%20%60ValueError%60.%20Then%20run%20the%0A%20%20%20%20same%20line%20without%20the%20%60try%60%20and%20compare%20what%20you%20see.%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%20mo.md(r%22%22%22%0A%20%20%20%20%23%23%23%20A%20loop%20that%20survives%20a%20bad%20row%0A%0A%20%20%20%20This%20is%20the%20shape%20that%20matters%20in%20analytics.%20You%20are%20working%20through%20a%20list%2C%20one%20item%0A%20%20%20%20fails%2C%20and%20you%20want%20the%20other%20400%20anyway.%20Put%20the%20%60try%60%20**inside**%20the%20loop%20and%0A%20%20%20%20collect%20the%20failures.%0A%20%20%20%20%22%22%22)%0A%20%20%20%20return%0A%0A%0A%40app.cell%0Adef%20_(work_dir)%3A%0A%20%20%20%20_wanted%20%3D%20%5B%22france-2011.csv%22%2C%20%22summary.json%22%2C%20%22germany-2011.csv%22%2C%20%22italy-2011.csv%22%5D%0A%0A%20%20%20%20found%2C%20failed%20%3D%20%5B%5D%2C%20%5B%5D%0A%20%20%20%20for%20_name%20in%20_wanted%3A%0A%20%20%20%20%20%20%20%20try%3A%0A%20%20%20%20%20%20%20%20%20%20%20%20_size%20%3D%20(work_dir%20%2F%20_name).stat().st_size%0A%20%20%20%20%20%20%20%20%20%20%20%20found.append(_name)%0A%20%20%20%20%20%20%20%20except%20FileNotFoundError%3A%0A%20%20%20%20%20%20%20%20%20%20%20%20failed.append(_name)%0A%0A%20%20%20%20f%22read%20%7Blen(found)%7D%20of%20%7Blen(_wanted)%7D%2C%20missing%3A%20%7B'%2C%20'.join(failed)%7D%22%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%22%0A%20%20%20%20Two%20lists%20come%20out%20instead%20of%20one%2C%20and%20the%20second%20one%20is%20the%20report.%20A%20loop%20that%0A%20%20%20%20prints%20an%20error%20and%20moves%20on%20gives%20you%20neither.%0A%0A%20%20%20%20%F0%9F%91%89%20**Try%20it%3A**%20move%20the%20%60try%60%20to%20the%20outside%20of%20the%20loop%2C%20so%20it%20wraps%20the%20whole%0A%20%20%20%20%60for%60.%20Run%20it%20and%20count%20how%20many%20files%20it%20manages%20to%20read%20before%20it%20gives%20up.%0A%0A%20%20%20%20**The%20trap.**%20Catching%20an%20error%20and%20continuing%20is%20a%20decision%20to%20produce%20an%20answer%0A%20%20%20%20from%20incomplete%20data.%20That%20decision%20belongs%20in%20the%20memo%2C%20next%20to%20the%20number.%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%20mo.md(r%22%22%22%0A%20%20%20%20%23%23%23%20%60assert%60%20stops%20the%20code%20when%20the%20data%20is%20not%20what%20you%20believe%0A%0A%20%20%20%20%60try%60%20%2F%20%60except%60%20is%20for%20the%20failure%20that%20announces%20itself.%20%60assert%60%20is%20for%20the%20one%0A%20%20%20%20that%20does%20not.%20You%20write%20down%20what%20you%20expect%20to%20be%20true%2C%20and%20Python%20raises%0A%20%20%20%20%60AssertionError%60%20the%20moment%20it%20stops%20being%20true.%0A%0A%20%20%20%20Three%20things%20are%20worth%20asserting%20on%20every%20dataset%2C%20and%20all%20three%20appear%20again%20in%0A%20%20%20%20sections%2026%20and%2027%3A%0A%0A%20%20%20%20-%20**A%20row%20count.**%20How%20many%20rows%20should%20this%20have%2C%20and%20does%20it.%0A%20%20%20%20-%20**A%20total.**%20Does%20the%20number%20you%20are%20about%20to%20report%20agree%20with%20a%20second%20way%20of%0A%20%20%20%20%20%20computing%20it.%0A%20%20%20%20-%20**A%20join%20cardinality.**%20Did%20a%20join%20change%20the%20row%20count%20when%20it%20had%20no%20business%0A%20%20%20%20%20%20changing%20it.%0A%0A%20%20%20%20This%20is%20what%20the%20course's%20*How%20I%20know%20these%20numbers%20are%20right*%20section%20runs%20on.%20An%0A%20%20%20%20assert%20that%20holds%20is%20a%20check%20somebody%20else%20can%20rerun.%0A%20%20%20%20%22%22%22)%0A%20%20%20%20return%0A%0A%0A%40app.cell%0Adef%20_(retail)%3A%0A%20%20%20%20assert%20retail.height%20%3D%3D%20541_909%2C%20%22the%20file%20is%20not%20the%20one%20this%20chapter%20was%20written%20for%22%0A%20%20%20%20assert%20retail%5B%22Quantity%22%5D.null_count()%20%3D%3D%200%2C%20%22Quantity%20has%20gaps%22%0A%20%20%20%20assert%20(retail%5B%22InvoiceDate%22%5D.dt.year()%20%3E%3D%202010).all()%2C%20%22a%20date%20before%20the%20data%20starts%22%0A%0A%20%20%20%20_is_cancelled%20%3D%20retail%5B%22InvoiceNo%22%5D.str.starts_with(%22C%22)%0A%20%20%20%20_cancelled%20%3D%20_is_cancelled.sum()%0A%20%20%20%20_negative%20%3D%20(retail%5B%22Quantity%22%5D%20%3C%200).sum()%0A%20%20%20%20assert%20(retail.filter(_is_cancelled)%5B%22Quantity%22%5D%20%3C%200).all()%2C%20%22a%20cancelled%20line%20with%20a%20quantity%20of%20zero%20or%20more%22%0A%0A%20%20%20%20f%224%20checks%20passed.%20%7B_cancelled%3A%2C%7D%20cancelled%20lines%2C%20%7B_negative%3A%2C%7D%20negative%20quantities.%22%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%22%0A%20%20%20%20The%20last%20one%20is%20the%20interesting%20kind.%20It%20is%20not%20a%20fact%20about%20the%20file%20format%3B%20it%20is%20a%0A%20%20%20%20claim%20about%20the%20business%2C%20that%20a%20cancellation%20always%20shows%20up%20as%20a%20negative%20quantity.%0A%20%20%20%20It%20holds%20here%2C%20and%201%2C336%20negative%20rows%20are%20not%20cancellations%2C%20which%20is%20a%20question%0A%20%20%20%20worth%20asking.%0A%0A%20%20%20%20When%20an%20assert%20fails%20you%20get%20the%20message%20you%20wrote%3A%0A%0A%20%20%20%20%60%60%60python%0A%20%20%20%20assert%20joined.height%20%3D%3D%20orders.height%2C%20f%22join%20changed%20the%20row%20count%3A%20%7Borders.height%7D%20to%20%7Bjoined.height%7D%22%0A%20%20%20%20%60%60%60%0A%0A%20%20%20%20%60%60%60%0A%20%20%20%20AssertionError%3A%20join%20changed%20the%20row%20count%3A%205%20to%2014%0A%20%20%20%20%60%60%60%0A%0A%20%20%20%20%F0%9F%91%89%20**Try%20it%3A**%20write%20an%20assert%20that%20is%20false%20on%20purpose%2C%20with%20a%20message%20that%20names%0A%20%20%20%20both%20numbers.%20Then%20delete%20the%20message%20and%20run%20it%20again.%20Compare%20how%20long%20each%20version%0A%20%20%20%20takes%20to%20understand.%0A%0A%20%20%20%20**The%20trap.**%20%60assert%60%20disappears%20when%20Python%20is%20run%20with%20the%20%60-O%60%20flag%2C%20so%20it%20is%20a%0A%20%20%20%20development%20check%20and%20not%20a%20production%20guard.%20In%20a%20notebook%20it%20is%20exactly%20the%20right%0A%20%20%20%20tool%2C%20and%20in%20shipped%20code%20you%20use%20%60if%20...%3A%20raise%20ValueError(...)%60%20instead.%0A%0A%20%20%20%20%23%23%23%20%E2%9C%94%20Check%20your%20understanding%0A%0A%20%20%20%201.%20A%20%60try%60%20block%20wraps%20a%20whole%20%60for%60%20loop.%20The%20third%20of%20400%20files%20is%20missing.%20How%20many%0A%20%20%20%20%20%20%20files%20get%20read%3F%0A%20%20%20%202.%20Name%20a%20failure%20that%20%60except%3A%60%20would%20catch%20and%20that%20you%20would%20rather%20have%20seen.%0A%20%20%20%203.%20You%20join%20two%20tables%20and%20the%20row%20count%20goes%20from%205%20to%2014.%20Which%20of%20the%20two%20tools%20in%0A%20%20%20%20%20%20%20this%20section%20catches%20it%2C%20and%20why%20does%20the%20other%20one%20not%3F%0A%20%20%20%204.%20Give%20one%20assert%20you%20could%20write%20about%20a%20dataset%20before%20you%20have%20looked%20at%20it%20at%0A%20%20%20%20%20%20%20all.%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%20mo.md(r%22%22%22%0A%20%20%20%20---%0A%20%20%20%20%23%23%2023.%20Series%20and%20DataFrame%0A%0A%20%20%20%20A%20**DataFrame**%20is%20a%20table%3A%20named%20columns%2C%20rows%2C%20one%20type%20per%20column%2C%20which%20polars%0A%20%20%20%20calls%20the%20column's%20**dtype**.%20A%20**Series**%20is%20one%20of%20those%20columns.%20Every%20polars%0A%20%20%20%20operation%20in%20this%20chapter%20either%20produces%20one%20of%20the%20two%20or%20asks%20one%20of%20them%20a%20question.%0A%0A%20%20%20%20%F0%9F%93%96%20**Used%20in%3A**%20From%20a%20CSV%20file%20to%20a%20dataframe%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%20mo.md(r%22%22%22%0A%20%20%20%20%23%23%23%20Questions%20to%20ask%20a%20frame%20you%20have%20never%20seen%0A%0A%20%20%20%20Shape%2C%20column%20names%2C%20types%2C%20and%20the%20first%20few%20rows.%20In%20that%20order%2C%20before%20anything%0A%20%20%20%20else%2C%20every%20time.%0A%20%20%20%20%22%22%22)%0A%20%20%20%20return%0A%0A%0A%40app.cell%0Adef%20_(retail)%3A%0A%20%20%20%20_rows%2C%20_cols%20%3D%20retail.shape%0A%0A%20%20%20%20f%22%7B_rows%3A%2C%7D%20rows%20and%20%7B_cols%7D%20columns%3A%20%7B'%2C%20'.join(retail.columns)%7D%22%0A%20%20%20%20return%0A%0A%0A%40app.cell%0Adef%20_(retail)%3A%0A%20%20%20%20retail.head(3)%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%22%0A%20%20%20%20%60retail.schema%60%20answered%20the%20third%20question%20in%20section%2021.%20A%20row%20has%20no%20name%2C%20only%20a%0A%20%20%20%20position%2C%20so%20there%20is%20no%20column%20of%20labels%20down%20the%20left.%20That%20is%20the%20largest%0A%20%20%20%20difference%20between%20polars%20and%20pandas%2C%20and%20section%2030%20is%20where%20it%20matters.%0A%0A%20%20%20%20%F0%9F%91%89%20**Try%20it%3A**%20run%20%60retail.describe()%60.%20It%20summarizes%20every%20column%20in%20one%20table%2C%20and%0A%20%20%20%20one%20of%20the%20numbers%20it%20reports%20for%20%60Quantity%60%20is%20alarming.%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%20mo.md(r%22%22%22%0A%20%20%20%20%23%23%23%20A%20column%20that%20looks%20numeric%20and%20is%20not%0A%0A%20%20%20%20%60InvoiceNo%60%20is%20six%20digits%20on%20most%20rows.%20On%209%2C288%20of%20them%20it%20is%20six%20digits%20with%20a%20%60C%60%0A%20%20%20%20in%20front%2C%20and%20the%20%60C%60%20means%20the%20invoice%20was%20cancelled.%20So%20the%20column%20is%20text%2C%20and%20any%0A%20%20%20%20code%20that%20treats%20it%20as%20a%20number%20either%20fails%20or%20throws%20the%20cancellations%20away.%0A%20%20%20%20Section%2021%20met%20it%20from%20the%20other%20side%2C%20when%20polars%20guessed%20a%20number%20and%20stopped.%0A%0A%20%20%20%20This%20was%20found%20by%20the%20file%20refusing%20to%20be%20written.%20Converting%20the%20column%20to%20a%20number%0A%20%20%20%20for%20storage%20failed%20twice%2C%20once%20for%20%60InvoiceNo%60%20and%20once%20for%20%60StockCode%60%2C%20and%20the%20error%0A%20%20%20%20named%20the%20value%20it%20choked%20on%3A%0A%0A%20%20%20%20%60%60%60%0A%20%20%20%20Could%20not%20convert%20'C536379'%20with%20type%20str%0A%20%20%20%20%60%60%60%0A%20%20%20%20%22%22%22)%0A%20%20%20%20return%0A%0A%0A%40app.cell%0Adef%20_(pl%2C%20retail)%3A%0A%20%20%20%20_c%20%3D%20retail.filter(pl.col(%22InvoiceNo%22).str.starts_with(%22C%22))%0A%0A%20%20%20%20(%0A%20%20%20%20%20%20%20%20retail.schema%5B%22InvoiceNo%22%5D%2C%0A%20%20%20%20%20%20%20%20f%22%7B_c.height%3A%2C%7D%20cancelled%20lines%22%2C%0A%20%20%20%20%20%20%20%20f%22first%20one%3A%20%7B_c%5B'InvoiceNo'%5D%5B0%5D%7D%2C%20quantity%20%7B_c%5B'Quantity'%5D%5B0%5D%7D%22%2C%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%22%0A%20%20%20%20%F0%9F%91%89%20**Try%20it%3A**%20find%20the%20pair%20%60581483%60%20and%20%60C581484%60%20in%20the%20data.%20One%20sells%2080%2C995%0A%20%20%20%20units%20of%20a%20paper%20craft%20kit%20and%20the%20other%20takes%20them%20back%2012%20minutes%20later.%20Then%0A%20%20%20%20compute%20%60retail%5B%22Quantity%22%5D.sum()%60%20and%20decide%20whether%20you%20would%20put%20that%20number%20in%20a%0A%20%20%20%20slide.%0A%0A%20%20%20%20**The%20trap.**%20An%20agent%20asked%20to%20clean%20this%20file%20will%20see%20a%20column%20called%20%60InvoiceNo%60%2C%0A%20%20%20%20call%20it%20an%20identifier%2C%20and%20**cast**%20it%20to%20an%20integer.%20The%20cast%20either%20raises%20or%0A%20%20%20%20silently%20turns%209%2C291%20invoice%20numbers%20into%20nulls%2C%20depending%20on%20how%20it%20was%20written.%20Read%20what%20it%0A%20%20%20%20did%20to%20the%20column.%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%20mo.md(r%22%22%22%0A%20%20%20%20%23%23%23%20A%20column%20or%20a%20frame%0A%0A%20%20%20%20One%20name%20in%20brackets%20gives%20a%20Series.%20%60.select()%60%20with%20one%20or%20more%20names%20gives%20a%20frame%2C%0A%20%20%20%20and%20it%20is%20the%20same%20word%2C%20doing%20the%20same%20job%2C%20as%20%60SELECT%60%20in%20SQL.%0A%20%20%20%20%22%22%22)%0A%20%20%20%20return%0A%0A%0A%40app.cell%0Adef%20_(retail)%3A%0A%20%20%20%20_one%20%3D%20retail%5B%22UnitPrice%22%5D%0A%20%20%20%20_two%20%3D%20retail.select(%22Quantity%22%2C%20%22UnitPrice%22)%0A%0A%20%20%20%20type(_one).__name__%2C%20type(_two).__name__%2C%20_one.shape%2C%20_two.shape%0A%20%20%20%20return%0A%0A%0A%40app.cell%0Adef%20_(retail)%3A%0A%20%20%20%20_q%20%3D%20retail%5B%22Quantity%22%5D%0A%0A%20%20%20%20f%22sum%20%7B_q.sum()%3A%2C%7D%20%C2%B7%20mean%20%7B_q.mean()%3A.2f%7D%20%C2%B7%20min%20%7B_q.min()%3A%2C%7D%20%C2%B7%20max%20%7B_q.max()%3A%2C%7D%22%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%22%0A%20%20%20%20A%20minimum%20of%20-80%2C995%20and%20a%20maximum%20of%2080%2C995%20in%20a%20column%20called%20%60Quantity%60.%20The%0A%20%20%20%20summary%20took%20one%20line%20and%20it%20has%20already%20told%20you%20the%20file%20has%20returns%20in%20it.%0A%0A%20%20%20%20%F0%9F%91%89%20**Try%20it%3A**%20call%20%60.describe()%60%20on%20%60retail%5B%22UnitPrice%22%5D%60%20and%20then%20count%20how%20many%0A%20%20%20%20rows%20have%20a%20price%20of%20zero%20or%20less.%20The%20count%20is%202%2C517%20and%20none%20of%20them%20raised%0A%20%20%20%20anything.%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%20mo.md(r%22%22%22%0A%20%20%20%20%23%23%23%20Money%20that%20arrives%20as%20a%20decimal%0A%0A%20%20%20%20A%20parquet%20file%20can%20store%20money%20as%20a%20**decimal**%2C%20a%20type%20that%20does%20exact%20arithmetic%20and%0A%20%20%20%20never%20rounds%200.1%20%2B%200.2%20to%200.30000000000000004.%20Online%20Retail's%20%60UnitPrice%60%20is%20a%20plain%0A%20%20%20%20float%20and%20none%20of%20this%20touches%20it.%20The%20Northwind%20warehouse%20stores%20money%20as%20decimals%2C%0A%20%20%20%20and%20polars%20reads%20every%20money%20column%20there%20as%20%60Decimal%60.%0A%20%20%20%20%22%22%22)%0A%20%20%20%20return%0A%0A%0A%40app.cell%0Adef%20_(BytesIO%2C%20mo%2C%20pl%2C%20urlopen)%3A%0A%20%20%20%20def%20northwind(name)%3A%0A%20%20%20%20%20%20%20%20%22%22%22One%20Northwind%20table.%20Repository%20copy%20first%2C%20published%20copy%20second.%22%22%22%0A%20%20%20%20%20%20%20%20_here%20%3D%20mo.notebook_dir()%0A%20%20%20%20%20%20%20%20_local%20%3D%20next(%0A%20%20%20%20%20%20%20%20%20%20%20%20(p%20%2F%20%22data%22%20%2F%20%22northwind%22%20%2F%20f%22%7Bname%7D.parquet%22%20for%20p%20in%20%5B_here%2C%20*_here.parents%5D%0A%20%20%20%20%20%20%20%20%20%20%20%20%20if%20(p%20%2F%20%22data%22%20%2F%20%22northwind%22).is_dir())%2C%0A%20%20%20%20%20%20%20%20%20%20%20%20None%2C%0A%20%20%20%20%20%20%20%20)%0A%20%20%20%20%20%20%20%20if%20_local%20is%20not%20None%3A%0A%20%20%20%20%20%20%20%20%20%20%20%20return%20pl.read_parquet(_local)%0A%20%20%20%20%20%20%20%20_url%20%3D%20f%22https%3A%2F%2Foim7510.github.io%2Fdata%2Fnorthwind%2F%7Bname%7D.parquet%22%0A%20%20%20%20%20%20%20%20return%20pl.read_parquet(BytesIO(urlopen(_url).read()))%0A%0A%20%20%20%20return%20(northwind%2C)%0A%0A%0A%40app.cell%0Adef%20_(northwind)%3A%0A%20%20%20%20nw_lines%20%3D%20northwind(%22order-details%22)%0A%0A%20%20%20%20nw_lines.schema%0A%20%20%20%20return%20(nw_lines%2C)%0A%0A%0A%40app.cell(hide_code%3DTrue)%0Adef%20_(mo)%3A%0A%20%20%20%20mo.md(r%22%22%22%0A%20%20%20%20%60UnitPrice%60%20is%20%60Decimal%60%2C%20exact%20to%20four%20places.%20Now%20multiply%20it%20three%20ways.%0A%20%20%20%20%22%22%22)%0A%20%20%20%20return%0A%0A%0A%40app.cell%0Adef%20_(nw_lines%2C%20pl)%3A%0A%20%20%20%20_three%20%3D%20nw_lines.head(1).select(%0A%20%20%20%20%20%20%20%20by_quantity%3Dpl.col(%22UnitPrice%22)%20*%20pl.col(%22Quantity%22)%2C%0A%20%20%20%20%20%20%20%20by_rate%3Dpl.col(%22UnitPrice%22)%20*%201.05%2C%0A%20%20%20%20%20%20%20%20after_discount%3Dpl.col(%22UnitPrice%22)%20*%20pl.col(%22Quantity%22)%20*%20(1%20-%20pl.col(%22Discount%22))%2C%0A%20%20%20%20)%0A%0A%20%20%20%20_three.schema%2C%20_three.row(0)%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%22%0A%20%20%20%20Nothing%20raised.%20Decimal%20times%20a%20whole%20number%20stays%20a%20decimal.%20Decimal%20times%20a%20float%0A%20%20%20%20becomes%20a%20float%2C%20and%20%6014%20*%201.05%60%20comes%20back%20as%20%6014.700000000000001%60.%20So%20the%20line%20total%0A%20%20%20%20is%20exact%20and%20the%20line%20total%20after%20a%20discount%20is%20a%20float%2C%20off%20the%20same%20column%2C%20because%0A%20%20%20%20%60Discount%60%20is%20a%20float.%0A%0A%20%20%20%20It%20gets%20quieter%20than%20that.%0A%20%20%20%20%22%22%22)%0A%20%20%20%20return%0A%0A%0A%40app.cell%0Adef%20_(nw_lines)%3A%0A%20%20%20%20_first%20%3D%20nw_lines%5B%22UnitPrice%22%5D.head(100)%0A%0A%20%20%20%20f%22sum%3A%20%7B_first.sum()!r%7D%22%2C%20f%22mean%3A%20%7B_first.mean()!r%7D%22%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%22%0A%20%20%20%20The%20same%20column%2C%20two%20aggregates.%20%60.sum()%60%20hands%20back%20a%20%60Decimal%60%20and%20%60.mean()%60%20hands%0A%20%20%20%20back%20a%20float%2C%20because%20the%20mean%20divides.%20Put%20those%20two%20numbers%20in%20one%20report%20and%20they%0A%20%20%20%20behave%20differently%20the%20next%20time%20somebody%20multiplies%20one%20of%20them.%0A%0A%20%20%20%20**The%20fix%20is%20one%20call%2C%20and%20you%20make%20it%20at%20read%20time.**%20Cast%20the%20money%20columns%20to%0A%20%20%20%20%60Float64%60%20as%20soon%20as%20the%20frame%20arrives%2C%20and%20write%20down%20that%20you%20did%2C%20because%20you%20have%0A%20%20%20%20just%20accepted%20floating%20point%20rounding%20on%20money%20in%20exchange%20for%20one%20type%20everywhere.%0A%20%20%20%20%22%22%22)%0A%20%20%20%20return%0A%0A%0A%40app.cell%0Adef%20_(nw_lines%2C%20pl)%3A%0A%20%20%20%20nw_priced%20%3D%20nw_lines.cast(%7B%22UnitPrice%22%3A%20pl.Float64%7D).with_columns(%0A%20%20%20%20%20%20%20%20LineTotal%3Dpl.col(%22UnitPrice%22)%20*%20pl.col(%22Quantity%22)%20*%20(1%20-%20pl.col(%22Discount%22))%0A%20%20%20%20)%0A%0A%20%20%20%20_line%20%3D%20nw_priced.row(0%2C%20named%3DTrue)%0A%20%20%20%20f%22%7B_line%5B'Quantity'%5D%7D%20at%20%7B_line%5B'UnitPrice'%5D%3A.2f%7D%20comes%20to%20%7B_line%5B'LineTotal'%5D%3A.2f%7D%22%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%22%0A%20%20%20%20That%20is%20the%20Queso%20Cabrales%20line%20on%20order%20%6010248%60%3A%2012%20at%2014.00%2C%20so%20168.00%2C%20which%20you%20can%0A%20%20%20%20check%20by%20hand.%0A%0A%20%20%20%20**The%20other%20route.**%20Reading%20the%20same%20file%20through%20DuckDB%20with%0A%20%20%20%20%60duckdb.sql(%22SELECT%20*%20FROM%20'order-details.parquet'%22).pl()%60%20keeps%20the%20%60Decimal%60.%20Ending%0A%20%20%20%20the%20same%20line%20in%20%60.df()%60%20hands%20you%20a%20pandas%20frame%20with%20%60float64%60%20instead%2C%20because%20pandas%0A%20%20%20%20has%20no%20decimal%20column.%20Same%20file%2C%20same%20rows%2C%20different%20type%2C%20and%20nothing%20anywhere%0A%20%20%20%20reports%20the%20difference.%20If%20your%20numbers%20disagree%20with%20a%20classmate's%20in%20the%20fourth%0A%20%20%20%20decimal%20place%2C%20this%20is%20the%20first%20thing%20to%20check.%0A%0A%20%20%20%20%F0%9F%91%89%20**Try%20it%3A**%20read%20one%20Northwind%20money%20column%20both%20ways%2C%20%60.pl()%60%20and%20%60.df()%60%2C%20and%20print%0A%20%20%20%20its%20type%20from%20each.%20Then%20multiply%20the%20first%20value%20of%20each%20by%20%601.05%60.%0A%0A%20%20%20%20%23%23%23%20%E2%9C%94%20Check%20your%20understanding%0A%0A%20%20%20%201.%20%60retail%5B%22Quantity%22%5D%60%20and%20%60retail.select(%22Quantity%22)%60%20name%20the%20same%20column.%20What%20is%0A%20%20%20%20%20%20%20different%20about%20what%20comes%20back%3F%0A%20%20%20%202.%20%60InvoiceNo%60%20holds%20%60536365%60%20and%20%60C536379%60.%20Give%20one%20thing%20that%20breaks%20if%20the%20column%0A%20%20%20%20%20%20%20is%20stored%20as%20an%20integer.%0A%20%20%20%203.%20A%20%60Decimal%60%20money%20column%20times%20a%20quantity%20stays%20exact%2C%20and%20times%20a%20discount%20rate%20it%0A%20%20%20%20%20%20%20does%20not.%20What%20decides%20which%20one%20you%20get%3F%0A%20%20%20%204.%20%60.sum()%60%20returns%20a%20%60Decimal%60%20and%20%60.mean()%60%20returns%20a%20float%20off%20the%20same%20column.%0A%20%20%20%20%20%20%20Why%20would%20that%20be%20hard%20to%20notice%3F%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%20mo.md(r%22%22%22%0A%20%20%20%20---%0A%20%20%20%20%23%23%2024.%20Selecting%2C%20filtering%2C%20sorting%0A%0A%20%20%20%20Three%20operations%20answer%20most%20of%20what%20gets%20asked%20of%20a%20table.%20Take%20the%20columns%20you%20want%2C%0A%20%20%20%20keep%20the%20rows%20that%20qualify%2C%20and%20put%20them%20in%20an%20order%20that%20lifts%20the%20answer%20to%20the%20top.%0A%20%20%20%20All%20three%20hand%20back%20a%20new%20frame%20and%20leave%20the%20original%20where%20it%20was.%0A%0A%20%20%20%20%F0%9F%93%96%20**Used%20in%3A**%20From%20a%20CSV%20file%20to%20a%20dataframe%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%20mo.md(r%22%22%22%0A%20%20%20%20%23%23%23%20A%20comparison%20on%20a%20column%20gives%20a%20column%20of%20%60True%60%20and%20%60False%60%0A%0A%20%20%20%20%60retail%5B%22Country%22%5D%20%3D%3D%20%22France%22%60%20does%20not%20give%20one%20answer.%20It%20gives%20541%2C909%20of%20them%2C%20one%0A%20%20%20%20per%20row%2C%20as%20a%20Series%20of%20booleans.%20That%20Series%20is%20called%20a%20**mask**%2C%20and%20%60.sum()%60%20counts%0A%20%20%20%20it%20because%20%60True%60%20adds%20one%20and%20%60False%60%20adds%20nothing.%0A%20%20%20%20%22%22%22)%0A%20%20%20%20return%0A%0A%0A%40app.cell%0Adef%20_(retail)%3A%0A%20%20%20%20_is_france%20%3D%20retail%5B%22Country%22%5D%20%3D%3D%20%22France%22%0A%0A%20%20%20%20_is_france.dtype%2C%20f%22%7B_is_france.sum()%3A%2C%7D%20rows%22%2C%20f%22%7B_is_france.mean()%3A.2%25%7D%20of%20the%20file%22%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%22%0A%20%20%20%20%60.filter()%60%20keeps%20the%20rows%20where%20a%20condition%20is%20%60True%60.%20Write%20the%20condition%20with%0A%20%20%20%20%60pl.col(%22Country%22)%60%2C%20which%20means%20*the%20%60Country%60%20column%20of%20whichever%20frame%20this%20runs%0A%20%20%20%20on*%2C%20so%20the%20frame's%20name%20is%20written%20once.%20It%20is%20%60WHERE%60%20in%20SQL.%0A%20%20%20%20%22%22%22)%0A%20%20%20%20return%0A%0A%0A%40app.cell%0Adef%20_(pl%2C%20retail)%3A%0A%20%20%20%20retail.filter(pl.col(%22Country%22)%20%3D%3D%20%22France%22).head(3)%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%22%0A%20%20%20%20%F0%9F%91%89%20**Try%20it%3A**%20build%20the%20mask%20for%20Germany%2C%20count%20it%2C%20and%20take%20its%20%60.mean()%60.%20Then%20filter%0A%20%20%20%20on%20%60pl.col(%22Quantity%22)%20%3C%200%60%20and%20read%20what%20share%20of%20the%20file%20that%20is.%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%20mo.md(r%22%22%22%0A%20%20%20%20%23%23%23%20Combining%20conditions%20with%20%60%26%60%2C%20%60%7C%60%20and%20%60~%60%0A%0A%20%20%20%20%60%26%60%20is%20and%2C%20%60%7C%60%20is%20or%2C%20%60~%60%20is%20not.%20The%20English%20words%20do%20something%20else%20here%20and%20say%20so%0A%20%20%20%20loudly.%0A%0A%20%20%20%20%60%60%60python%0A%20%20%20%20retail.filter((pl.col(%22Country%22)%20%3D%3D%20%22France%22)%20and%20(pl.col(%22Quantity%22)%20%3E%20100))%0A%20%20%20%20%60%60%60%0A%0A%20%20%20%20%60%60%60%0A%20%20%20%20TypeError%3A%20the%20truth%20value%20of%20an%20Expr%20is%20ambiguous%0A%20%20%20%20%60%60%60%0A%0A%20%20%20%20%60and%60%20wants%20one%20yes%20or%20one%20no%2C%20and%20a%20condition%20on%20a%20column%20holds%20half%20a%20million%20of%0A%20%20%20%20them.%20%60%26%60%20compares%20row%20by%20row%2C%20which%20is%20the%20question%20you%20meant.%0A%0A%20%20%20%20The%20parentheses%20are%20load-bearing.%20%60%26%60%20binds%20tighter%20than%20%60%3D%3D%60%20and%20%60%3E%60%2C%20so%20without%20them%0A%20%20%20%20Python%20tries%20to%20combine%20%60%22France%22%60%20with%20%60pl.col(%22Quantity%22)%60%2C%20and%20you%20get%20the%20same%0A%20%20%20%20message%20as%20above%20for%20a%20different%20mistake.%0A%0A%20%20%20%20Inside%20%60.filter()%60%2C%20conditions%20separated%20by%20commas%20are%20joined%20with%20and%2C%20which%20is%20the%0A%20%20%20%20form%20the%20cleaning%20rule%20below%20uses.%0A%20%20%20%20%22%22%22)%0A%20%20%20%20return%0A%0A%0A%40app.cell%0Adef%20_(pl%2C%20retail)%3A%0A%20%20%20%20_bulk_france%20%3D%20retail.filter((pl.col(%22Country%22)%20%3D%3D%20%22France%22)%20%26%20(pl.col(%22Quantity%22)%20%3E%20100))%0A%20%20%20%20_not_uk%20%3D%20retail.filter(~(pl.col(%22Country%22)%20%3D%3D%20%22United%20Kingdom%22))%0A%0A%20%20%20%20(%0A%20%20%20%20%20%20%20%20f%22%7B_bulk_france.height%3A%2C%7D%20French%20lines%20of%20more%20than%20100%20units%22%2C%0A%20%20%20%20%20%20%20%20f%22%7B_not_uk.height%3A%2C%7D%20lines%20outside%20the%20United%20Kingdom%22%2C%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%22%0A%20%20%20%20%F0%9F%91%89%20**Try%20it%3A**%20write%20one%20condition%20for%20lines%20that%20are%20French%20**or**%20German%2C%20and%20one%20for%0A%20%20%20%20lines%20that%20are%20French%20**and**%20cancelled.%20Predict%20which%20keeps%20more%20rows%20before%20you%20run%0A%20%20%20%20either.%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%20mo.md(r%22%22%22%0A%20%20%20%20%23%23%23%20%60.select()%60%20and%20%60.filter()%60%20chain%0A%0A%20%20%20%20Each%20call%20hands%20back%20a%20frame%2C%20so%20the%20next%20call%20goes%20straight%20on%20the%20end.%20Rows%20first%2C%0A%20%20%20%20then%20the%20four%20columns%20you%20want%20to%20read%2C%20then%20the%20first%20three.%0A%20%20%20%20%22%22%22)%0A%20%20%20%20return%0A%0A%0A%40app.cell%0Adef%20_(pl%2C%20retail)%3A%0A%20%20%20%20_wanted%20%3D%20%5B%22InvoiceNo%22%2C%20%22Description%22%2C%20%22Quantity%22%2C%20%22UnitPrice%22%5D%0A%0A%20%20%20%20retail.filter(pl.col(%22Country%22)%20%3D%3D%20%22France%22).select(_wanted).head(3)%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%22%0A%20%20%20%20%F0%9F%91%89%20**Try%20it%3A**%20select%20a%20column%20that%20does%20not%20exist%20and%20read%20the%20error%3B%20it%20lists%20the%0A%20%20%20%20columns%20that%20do.%20Then%20run%0A%20%20%20%20%60retail.filter(pl.col(%22Quantity%22)%20%3E%201000).select(%22Description%22%2C%20%22Quantity%22)%60%20and%20see%0A%20%20%20%20how%20few%20rows%20come%20back.%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%20mo.md(r%22%22%22%0A%20%20%20%20%23%23%23%20Decisions%20that%20produce%20a%20frame%20you%20can%20add%20up%0A%0A%20%20%20%20Section%2023%20found%20three%20things%20in%20this%20file%20that%20stop%20a%20plain%20%60SUM%60%20from%20meaning%0A%20%20%20%20anything%3A%20cancelled%20invoices%2C%20negative%20quantities%2C%20and%20prices%20at%20or%20below%20zero.%20Handling%0A%20%20%20%20them%20is%20three%20conditions%20in%20one%20%60.filter()%60%2C%20and%20each%20one%20is%20a%20decision%20somebody%20has%20to%0A%20%20%20%20defend.%0A%0A%20%20%20%20Write%20them%20as%20three%20lines%20with%20three%20comments%2C%20so%20the%20decisions%20are%20readable%20by%20the%0A%20%20%20%20person%20who%20reviews%20the%20memo.%0A%20%20%20%20%22%22%22)%0A%20%20%20%20return%0A%0A%0A%40app.cell%0Adef%20_(pl%2C%20retail)%3A%0A%20%20%20%20sales%20%3D%20retail.filter(%0A%20%20%20%20%20%20%20%20~pl.col(%22InvoiceNo%22).str.starts_with(%22C%22)%2C%20%20%20%23%20decision%201%3A%20cancellations%20are%20out%0A%20%20%20%20%20%20%20%20pl.col(%22Quantity%22)%20%3E%200%2C%20%20%20%20%20%20%20%20%20%20%20%20%20%20%20%20%20%20%20%20%20%20%23%20decision%202%3A%20returns%20and%20empty%20lines%20are%20out%0A%20%20%20%20%20%20%20%20pl.col(%22UnitPrice%22)%20%3E%200%2C%20%20%20%20%20%20%20%20%20%20%20%20%20%20%20%20%20%20%20%20%20%23%20decision%203%3A%20free%20and%20negative%20prices%20are%20out%0A%20%20%20%20).with_columns(Revenue%3Dpl.col(%22Quantity%22)%20*%20pl.col(%22UnitPrice%22))%0A%0A%20%20%20%20(%0A%20%20%20%20%20%20%20%20f%22%7Bsales.height%3A%2C%7D%20rows%20kept%2C%20%7Bretail.height%20-%20sales.height%3A%2C%7D%20dropped%22%2C%0A%20%20%20%20%20%20%20%20f%22revenue%3A%20%7Bsales%5B'Revenue'%5D.sum()%3A%2C.2f%7D%22%2C%0A%20%20%20%20)%0A%20%20%20%20return%20(sales%2C)%0A%0A%0A%40app.cell(hide_code%3DTrue)%0Adef%20_(mo)%3A%0A%20%20%20%20mo.md(r%22%22%22%0A%20%20%20%20%60.with_columns()%60%20returns%20a%20new%20frame%20with%20the%20extra%20column.%20%60retail%60%20is%20unchanged%2C%0A%20%20%20%20because%20no%20polars%20method%20changes%20the%20frame%20it%20was%20called%20on%2C%20so%20there%20is%20never%20a%20copy%0A%20%20%20%20to%20make%20first.%0A%0A%20%20%20%20%F0%9F%91%89%20**Try%20it%3A**%20run%20the%20three%20conditions%20separately%20and%20print%20how%20many%20rows%20each%20one%0A%20%20%20%20removes%20on%20its%20own.%20They%20overlap%2C%20so%20the%20three%20numbers%20add%20up%20to%20more%20than%2011%2C805.%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%20mo.md(r%22%22%22%0A%20%20%20%20%23%23%23%20Sorting%20puts%20the%20answer%20where%20you%20can%20see%20it%0A%0A%20%20%20%20%60.sort(%22Revenue%22%2C%20descending%3DTrue)%60%20returns%20a%20new%20frame%20in%20a%20new%20order.%20%60.head(6)%60%20is%0A%20%20%20%20what%20makes%20it%20readable%20on%20a%20frame%20this%20size.%0A%20%20%20%20%22%22%22)%0A%20%20%20%20return%0A%0A%0A%40app.cell%0Adef%20_(sales)%3A%0A%20%20%20%20_cols%20%3D%20%5B%22InvoiceNo%22%2C%20%22StockCode%22%2C%20%22Description%22%2C%20%22Quantity%22%2C%20%22UnitPrice%22%2C%20%22Revenue%22%5D%0A%0A%20%20%20%20sales.sort(%22Revenue%22%2C%20descending%3DTrue).head(6).select(_cols)%0A%20%20%20%20return%0A%0A%0A%40app.cell%0Adef%20_(pl%2C%20sales)%3A%0A%20%20%20%20_kept_cancelled%20%3D%20sales.filter(pl.col(%22InvoiceNo%22).is_in(%5B%22581483%22%2C%20%22541431%22%2C%20%22556444%22%5D))%0A%20%20%20%20_not_products%20%3D%20sales.filter(~pl.col(%22StockCode%22).str.contains(r%22%5E%5Cd%7B5%7D%22))%0A%0A%20%20%20%20(%0A%20%20%20%20%20%20%20%20f%22three%20lines%20cancelled%20minutes%20later%20and%20kept%20anyway%3A%20%7B_kept_cancelled%5B'Revenue'%5D.sum()%3A%2C.2f%7D%22%2C%0A%20%20%20%20%20%20%20%20f%22lines%20whose%20StockCode%20is%20not%20a%20five-digit%20product%20code%3A%20%7B_not_products.height%3A%2C%7D%22%2C%0A%20%20%20%20%20%20%20%20f%22and%20they%20carry%20%7B_not_products%5B'Revenue'%5D.sum()%3A%2C.2f%7D%20of%20the%20total%22%2C%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%22%0A%20%20%20%20Read%20the%20six%20rows%20above.%20The%20top%20three%20are%20the%20biggest%20revenue%20lines%20in%20the%20cleaned%20frame%0A%20%20%20%20and%20all%20three%20were%20cancelled%20within%2020%20minutes%20by%20an%20invoice%20starting%20%60C%60%2C%20which%20decision%201%0A%20%20%20%20dropped%20while%20keeping%20the%20sale%20it%20cancelled.%20%60AMAZONFEE%60%20is%20a%20marketplace%20fee.%20%60A563185%60%0A%20%20%20%20is%20an%20accounting%20entry%20whose%20description%20is%20*Adjust%20bad%20debt*%20and%20whose%20invoice%20number%0A%20%20%20%20starts%20with%20%60A%60%2C%20a%20prefix%20that%20appears%20three%20times%20in%20the%20whole%20file.%20%60POST%60%20is%20postage.%0A%0A%20%20%20%20None%20of%20the%20six%20largest%20lines%20in%20a%20column%20named%20%60Revenue%60%20is%20a%20product%20sale%20that%20stood%2C%0A%20%20%20%20and%20the%20three%20cleaning%20rules%20ran%20without%20raising%20anything.%0A%0A%20%20%20%20%F0%9F%91%89%20**Try%20it%3A**%20sort%20ascending%20instead%20and%20read%20the%20bottom%20six.%20Then%20sort%20by%20%60Quantity%60%0A%20%20%20%20and%20by%20%60UnitPrice%60%20and%20see%20whether%20the%20same%20rows%20keep%20appearing.%0A%0A%20%20%20%20**The%20trap.**%20A%20cleaning%20rule%20is%20a%20claim%20about%20the%20data.%20Sorting%20on%20the%20column%20the%20rule%0A%20%20%20%20was%20supposed%20to%20fix%20is%20how%20you%20test%20the%20claim%2C%20and%20it%20costs%20one%20line.%0A%0A%20%20%20%20%23%23%23%20%E2%9C%94%20Check%20your%20understanding%0A%0A%20%20%20%201.%20%60retail%5B%22Country%22%5D%20%3D%3D%20%22France%22%60%20gives%20back%20something%20with%20541%2C909%20entries.%20What%20is%20in%0A%20%20%20%20%20%20%20it%2C%20and%20what%20does%20%60.sum()%60%20on%20it%20mean%3F%0A%20%20%20%202.%20Two%20conditions%20go%20into%20%60.filter()%60%20once%20separated%20by%20a%20comma%20and%20once%20joined%20with%0A%20%20%20%20%20%20%20%60%26%60.%20Do%20the%20two%20keep%20the%20same%20rows%3F%20What%20would%20%60%7C%60%20keep%3F%0A%20%20%20%203.%20Three%20conditions%20removed%2011%2C805%20rows.%20Name%20one%20row%20that%20survived%20all%20three%20and%20should%0A%20%20%20%20%20%20%20not%20be%20in%20a%20revenue%20total.%0A%20%20%20%204.%20Somebody%20hands%20you%20a%20cleaned%20frame%20and%20a%20revenue%20figure.%20What%20is%20the%20first%20thing%20you%0A%20%20%20%20%20%20%20sort%20by%2C%20and%20what%20are%20you%20looking%20for%3F%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%20mo.md(r%22%22%22%0A%20%20%20%20---%0A%20%20%20%20%23%23%2025.%20A%20column%20of%20dates%0A%0A%20%20%20%20A%20date%20column%20arrives%20as%20text%20far%20more%20often%20than%20it%20arrives%20as%20a%20date%2C%20and%20text%20does%20not%0A%20%20%20%20know%20that%20November%20comes%20after%20October.%20This%20section%20parses%20the%20text%2C%20pulls%20parts%20out%20of%0A%20%20%20%20the%20result%2C%20and%20groups%20by%20month.%0A%0A%20%20%20%20%F0%9F%93%96%20**Used%20in%3A**%20Building%20and%20querying%20your%20database%20%C2%B7%20Building%20your%20product%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%20mo.md(r%22%22%22%0A%20%20%20%20%23%23%23%20A%20string%20slice%20looks%20like%20a%20month%0A%0A%20%20%20%20Take%20the%20seven%20leading%20characters%20of%20%602010-12-01%2008%3A26%60%20and%20you%20get%20%602010-12%60%2C%20which%20is%0A%20%20%20%20the%20month.%20That%20works%2C%20and%20it%20works%20only%20on%20that%20one%20way%20of%20writing%20a%20date.%0A%0A%20%20%20%20This%20course's%20own%20archive%20has%20it.%20A%20starter%20script%20tells%20the%20student%20to%20extract%20a%20month%0A%20%20%20%20with%20%60order_date%5B%3A7%5D%60%2C%20and%20the%20comment%20beside%20it%20reads%20%60'2024-01-15'%20%E2%86%92%20'2024-01'%60.%20The%0A%20%20%20%20comment%20is%20correct%20about%20that%20string.%0A%0A%20%20%20%20Here%20is%20the%20same%20slice%20on%20the%20same%20French%20rows%20written%20the%20way%20a%20spreadsheet%20exports%0A%20%20%20%20them.%0A%20%20%20%20%22%22%22)%0A%20%20%20%20return%0A%0A%0A%40app.cell%0Adef%20_(code_columns%2C%20pl%2C%20retail%2C%20work_dir)%3A%0A%20%20%20%20excel_csv_path%20%3D%20work_dir%20%2F%20%22france-excel.csv%22%0A%20%20%20%20retail.filter(pl.col(%22Country%22)%20%3D%3D%20%22France%22).write_csv(%0A%20%20%20%20%20%20%20%20excel_csv_path%2C%20datetime_format%3D%22%25m%2F%25d%2F%25Y%20%25H%3A%25M%22%0A%20%20%20%20)%0A%0A%20%20%20%20excel_dates%20%3D%20pl.read_csv(excel_csv_path%2C%20schema_overrides%3Dcode_columns)%0A%20%20%20%20excel_dates%5B%22InvoiceDate%22%5D.head(3).to_list()%2C%20excel_dates.schema%5B%22InvoiceDate%22%5D%0A%20%20%20%20return%20(excel_dates%2C)%0A%0A%0A%40app.cell%0Adef%20_(excel_dates)%3A%0A%20%20%20%20_sliced%20%3D%20excel_dates%5B%22InvoiceDate%22%5D.str.slice(0%2C%207)%0A%0A%20%20%20%20(%0A%20%20%20%20%20%20%20%20f%22first%20three%20slices%3A%20%7B_sliced.head(3).to_list()%7D%22%2C%0A%20%20%20%20%20%20%20%20f%22%7B_sliced.n_unique()%7D%20distinct%20'months'%22%2C%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%22%0A%20%20%20%20The%20slice%20ran%2C%20returned%20strings%2C%20and%20grouped%20nothing.%20%6012%2F01%2F2%60%20is%20a%20month%20and%20a%20day%20and%0A%20%20%20%20one%20digit%20of%20a%20year.%20Group%20by%20it%20and%20you%20get%20one%20bucket%20per%20calendar%20day.%0A%0A%20%20%20%20%F0%9F%91%89%20**Try%20it%3A**%20run%20the%20same%20slice%20on%20the%20parquet%20frame's%20%60InvoiceDate%60%20and%20read%20the%0A%20%20%20%20error.%20%60.str%60%20refuses%20a%20column%20that%20is%20already%20a%20date%2C%20which%20is%20the%20one%20place%20this%0A%20%20%20%20mistake%20announces%20itself.%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%20mo.md(r%22%22%22%0A%20%20%20%20%23%23%23%20Parsing%20with%20%60.str.to_datetime%60%0A%0A%20%20%20%20One%20call%20turns%20the%20text%20into%20a%20%60Datetime%60.%20Give%20it%20the%20format%20whenever%20you%20know%20how%20the%0A%20%20%20%20file%20writes%20a%20date%2C%20because%20the%20alternative%20is%20letting%20polars%20work%20it%20out%20from%20the%0A%20%20%20%20values.%0A%20%20%20%20%22%22%22)%0A%20%20%20%20return%0A%0A%0A%40app.cell%0Adef%20_(excel_dates)%3A%0A%20%20%20%20_parsed%20%3D%20excel_dates%5B%22InvoiceDate%22%5D.str.to_datetime(%22%25m%2F%25d%2F%25Y%20%25H%3A%25M%22)%0A%20%20%20%20_sliced%20%3D%20excel_dates%5B%22InvoiceDate%22%5D.str.slice(0%2C%207)%0A%0A%20%20%20%20(%0A%20%20%20%20%20%20%20%20f%22%7B_parsed.dtype%7D%2C%20%7B_parsed.min()%7D%20to%20%7B_parsed.max()%7D%22%2C%0A%20%20%20%20%20%20%20%20f%22real%20months%3A%20%7B_parsed.dt.strftime('%25Y-%25m').n_unique()%7D%22%2C%0A%20%20%20%20%20%20%20%20f%22months%20the%20string%20slice%20found%3A%20%7B_sliced.n_unique()%7D%22%2C%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%22%0A%20%20%20%20The%20file%20covers%2013%20months.%20The%20slice%20reported%20217%20of%20them.%0A%0A%20%20%20%20Now%20the%20reason%20the%20format%20matters.%20%6003%2F04%2F2011%60%20is%20a%20real%20date%20under%20two%20different%0A%20%20%20%20readings%2C%20and%20both%20parse.%0A%20%20%20%20%22%22%22)%0A%20%20%20%20return%0A%0A%0A%40app.cell%0Adef%20_(pl)%3A%0A%20%20%20%20_ambiguous%20%3D%20pl.Series(%5B%2203%2F04%2F2011%22%2C%20%2205%2F06%2F2011%22%5D)%0A%0A%20%20%20%20(%0A%20%20%20%20%20%20%20%20f%22month%20first%3A%20%7B_ambiguous.str.to_date('%25m%2F%25d%2F%25Y').to_list()%7D%22%2C%0A%20%20%20%20%20%20%20%20f%22day%20first%3A%20%20%20%7B_ambiguous.str.to_date('%25d%2F%25m%2F%25Y').to_list()%7D%22%2C%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%22%0A%20%20%20%20Two%20strings%20give%20four%20different%20dates%20and%20no%20error%20either%20way.%20Read%20in%20the%20wrong%20order%2C%0A%20%20%20%20a%20date%20on%20the%201st%20to%20the%2012th%20of%20a%20month%20becomes%20a%20different%20valid%20date%2C%20except%20on%20the%0A%20%20%20%20one%20day%20whose%20number%20equals%20the%20month%2C%20which%20reads%20the%20same%20both%20ways.%20From%20the%2013th%0A%20%20%20%20on%2C%20the%20day%20cannot%20be%20a%20month%2C%20and%20polars%20stops.%20So%20a%20file%20that%20parses%20without%20an%20error%0A%20%20%20%20has%20not%20proved%20the%20order%20right%3A%20if%20every%20date%20in%20it%20falls%20on%20the%2012th%20or%20earlier%2C%0A%20%20%20%20nothing%20stops%20it.%0A%0A%20%20%20%20A%20value%20that%20fits%20neither%20reading%20is%20different%3A%20polars%20stops%20and%20names%20it.%20%60strict%3DFalse%60%0A%20%20%20%20turns%20anything%20unparseable%20into%20a%20missing%20value%20instead%20of%20raising%2C%20which%20is%20a%20decision%0A%20%20%20%20to%20lose%20rows%20quietly%20and%20belongs%20beside%20a%20count%20of%20how%20many%20were%20lost.%20A%20**missing%0A%20%20%20%20value**%20is%20written%20%60null%60%20in%20polars%2C%20whatever%20the%20column's%20type.%0A%0A%20%20%20%20%F0%9F%91%89%20**Try%20it%3A**%20append%20the%20string%20%60%22not%20a%20date%22%60%20to%20the%20column%20with%20%60pl.concat%60%2C%20parse%20it%0A%20%20%20%20once%20as%20it%20is%20and%20once%20with%20%60strict%3DFalse%60%2C%20and%20count%20the%20missing%20values%20with%0A%20%20%20%20%60.null_count()%60.%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%20mo.md(r%22%22%22%0A%20%20%20%20%23%23%23%20%60.dt%60%20gets%20at%20the%20parts%0A%0A%20%20%20%20Once%20a%20column%20is%20a%20real%20date%2C%20%60.dt%60%20opens%20it%20up.%20%60.dt.year()%60%2C%20%60.dt.month()%60%2C%0A%20%20%20%20%60.dt.hour()%60%2C%20and%20%60.dt.strftime()%60%2C%20which%20writes%20the%20date%20out%20in%20any%20layout%20you%20name%3A%0A%20%20%20%20%60%22%25A%22%60%20for%20the%20weekday%2C%20%60%22%25Y-%25m%22%60%20for%20a%20year%20and%20month%20together.%0A%0A%20%20%20%20%60.dt%60%20is%20to%20a%20date%20column%20what%20%60.str%60%20is%20to%20a%20text%20column%2C%20and%20each%20one%20raises%20on%20the%0A%20%20%20%20other%20kind.%0A%20%20%20%20%22%22%22)%0A%20%20%20%20return%0A%0A%0A%40app.cell%0Adef%20_(pl%2C%20sales)%3A%0A%20%20%20%20dated%20%3D%20sales.with_columns(%0A%20%20%20%20%20%20%20%20Month%3Dpl.col(%22InvoiceDate%22).dt.strftime(%22%25Y-%25m%22)%2C%0A%20%20%20%20%20%20%20%20Weekday%3Dpl.col(%22InvoiceDate%22).dt.strftime(%22%25A%22)%2C%0A%20%20%20%20%20%20%20%20Hour%3Dpl.col(%22InvoiceDate%22).dt.hour()%2C%0A%20%20%20%20)%0A%0A%20%20%20%20dated.select(%22InvoiceDate%22%2C%20%22Month%22%2C%20%22Weekday%22%2C%20%22Hour%22).head(3)%0A%20%20%20%20return%20(dated%2C)%0A%0A%0A%40app.cell%0Adef%20_(dated)%3A%0A%20%20%20%20_days%20%3D%20dated%5B%22Weekday%22%5D.value_counts(sort%3DTrue)%0A%0A%20%20%20%20(%0A%20%20%20%20%20%20%20%20_days%2C%0A%20%20%20%20%20%20%20%20f%22%7B_days.height%7D%20distinct%20weekdays%20over%20%7Bdated.height%3A%2C%7D%20rows%22%2C%0A%20%20%20%20%20%20%20%20f%22trading%20hours%20run%20from%20%7Bdated%5B'Hour'%5D.min()%7D%20to%20%7Bdated%5B'Hour'%5D.max()%7D%22%2C%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%22%0A%20%20%20%20There%20are%20six%20weekdays%20here%2C%20and%20the%20missing%20one%20is%20Saturday.%20Every%20row%20in%20this%20file%20was%0A%20%20%20%20invoiced%20on%20one%20of%20six%20days%2C%20which%20is%20a%20fact%20about%20how%20the%20company%20operates.%0A%0A%20%20%20%20%F0%9F%91%89%20**Try%20it%3A**%20count%20rows%20by%20%60Hour%60%20and%20find%20the%20busiest%20hour%20of%20the%20day.%20Then%20check%0A%20%20%20%20whether%20Sunday%20looks%20like%20the%20other%20five%20days%20or%20like%20something%20else.%20Type%20the%0A%20%20%20%20number%20of%20rows%20in%20the%20busiest%20hour%20into%20the%20box%20below.%20The%20box%20answers%20only%20when%20you%0A%20%20%20%20run%20this%20chapter%20on%20your%20own%20computer.%0A%20%20%20%20%22%22%22)%0A%20%20%20%20return%0A%0A%0A%40app.cell%0Adef%20_(mo)%3A%0A%20%20%20%20found_it_box%20%3D%20mo.ui.text(label%3D%22Your%20number%22)%0A%20%20%20%20found_it_box%0A%20%20%20%20return%20(found_it_box%2C)%0A%0A%0A%40app.cell%0Adef%20_(found_it_box%2C%20mo)%3A%0A%20%20%20%20import%20base64%20as%20_base64%0A%20%20%20%20import%20hashlib%20as%20_hashlib%0A%0A%20%20%20%20_key%20%3D%20found_it_box.value.strip().replace(%22%2C%22%2C%20%22%22)%0A%20%20%20%20_lock%20%3D%20%22e606c26470ca67bf152b55b1b3639f7adea3a7cb3c17939340cb9d7a846348ec%22%0A%20%20%20%20_box%20%3D%20_base64.b64decode(%22B9%2F0LyGRvrlPTPe3H2SxiwW2YPzIRnRhFHRjdLIQ%2BHG6tAeOmZAcnJ8gOt0eejRbCPKQkbKmrF%2F7t%2FFKsb63Oe9PC4yCQkRa2y6rIUL1FKQ4CKcrIKeEevoUK%2FTjXGzjPb1I9g03%2B9ewgpfpoWp0X9UiHJ01COMuGuMg9fErTnkx5N8lw8dSiCb0vMmaW0vfWGrlgQ6MrDJYuKrz51olL4otNW6sL3QuDWrx7tUl28yaCj5trDof2pI1s%2F46mbR4llFkXwSg%22)%0A%20%20%20%20_stream%20%3D%20b%22%22.join(%0A%20%20%20%20%20%20%20%20_hashlib.sha256(f%22pad%3A%7B_key%7D%3A%7B_i%7D%22.encode()).digest()%0A%20%20%20%20%20%20%20%20for%20_i%20in%20range(len(_box)%20%2F%2F%2032%20%2B%201)%0A%20%20%20%20)%0A%20%20%20%20_opened%20%3D%20_hashlib.sha256(f%22lock%3A%7B_key%7D%22.encode()).hexdigest()%20%3D%3D%20_lock%0A%20%20%20%20mo.md(bytes(_a%20%5E%20_b%20for%20_a%2C%20_b%20in%20zip(_box%2C%20_stream)).decode())%20if%20_opened%20else%20None%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%22%0A%20%20%20%20%23%23%23%20Grouping%20by%20month%0A%0A%20%20%20%20%60%22%25Y-%25m%22%60%20writes%20the%20year%20first%2C%20so%20the%20months%20sort%20correctly%20as%20text.%20Group%20on%20it%2C%20sum%2C%0A%20%20%20%20and%20sort%2C%20because%20%60group_by%60%20hands%20its%20groups%20back%20in%20no%20fixed%20order%20(section%2026).%0A%20%20%20%20%22%22%22)%0A%20%20%20%20return%0A%0A%0A%40app.cell%0Adef%20_(dated%2C%20pl)%3A%0A%20%20%20%20monthly_revenue%20%3D%20(%0A%20%20%20%20%20%20%20%20dated.group_by(%22Month%22)%0A%20%20%20%20%20%20%20%20.agg(pl.col(%22Revenue%22).sum().round(2))%0A%20%20%20%20%20%20%20%20.sort(%22Month%22)%0A%20%20%20%20)%0A%0A%20%20%20%20monthly_revenue%0A%20%20%20%20return%20(monthly_revenue%2C)%0A%0A%0A%40app.cell%0Adef%20_(dated%2C%20pl)%3A%0A%20%20%20%20_last%20%3D%20dated.filter(pl.col(%22Month%22)%20%3D%3D%20%222011-12%22)%0A%0A%20%20%20%20(%0A%20%20%20%20%20%20%20%20f%222011-12%20runs%20%7B_last%5B'InvoiceDate'%5D.min()%7D%20to%20%7B_last%5B'InvoiceDate'%5D.max()%7D%22%2C%0A%20%20%20%20%20%20%20%20f%22%7B_last%5B'InvoiceDate'%5D.dt.day().n_unique()%7D%20trading%20days%20in%20it%2C%20against%20a%20full%20month%20everywhere%20else%22%2C%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%22%0A%20%20%20%20Read%20the%20last%20two%20numbers%20in%20that%20table%20together%20with%20the%20cell%20under%20it.%20November%202011%0A%20%20%20%20is%20the%20largest%20month%20in%20the%20file%20and%20December%202011%20is%20the%20smallest%20since%20April%2C%20which%0A%20%20%20%20looks%20like%20a%20collapse%20and%20is%20a%20file%20that%20stops%20on%20the%20ninth.%0A%0A%20%20%20%20Any%20month-over-month%20chart%2C%20growth%20rate%20or%20trend%20line%20drawn%20from%20this%20table%20without%0A%20%20%20%20dropping%20that%20last%20row%20is%20wrong%2C%20and%20every%20step%20of%20the%20calculation%20is%20correct.%0A%0A%20%20%20%20%F0%9F%91%89%20**Try%20it%3A**%20compute%20the%20month-over-month%20change%20with%0A%20%20%20%20%60monthly_revenue%5B%22Revenue%22%5D.pct_change()%60%20and%20read%20the%20last%20value.%20Then%20drop%20%602011-12%60%0A%20%20%20%20and%20read%20it%20again.%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%20mo.md(r%22%22%22%0A%20%20%20%20%23%23%23%20Asking%20for%20one%20day%0A%0A%20%20%20%20A%20timestamp%20carries%20a%20clock%20time%2C%20so%20%60%3D%3D%20datetime(2011%2C%201%2C%204)%60%20asks%20for%20that%20date%20at%0A%20%20%20%20exactly%20midnight.%20This%20file%20records%20nothing%20at%20midnight%2C%20so%20the%20answer%20is%20zero%20rows%20and%0A%20%20%20%20no%20error.%20polars%20refuses%20to%20compare%20a%20date%20column%20with%20a%20string%20such%20as%20%60%222011-01-04%22%60%0A%20%20%20%20at%20all%2C%20which%20rules%20out%20one%20mistake%20and%20leaves%20this%20one.%0A%20%20%20%20%22%22%22)%0A%20%20%20%20return%0A%0A%0A%40app.cell%0Adef%20_(date%2C%20dated%2C%20datetime%2C%20pl)%3A%0A%20%20%20%20_exact%20%3D%20dated.filter(pl.col(%22InvoiceDate%22)%20%3D%3D%20datetime(2011%2C%201%2C%204)).height%0A%20%20%20%20_by_date%20%3D%20dated.filter(pl.col(%22InvoiceDate%22).dt.date()%20%3D%3D%20date(2011%2C%201%2C%204)).height%0A%20%20%20%20_window%20%3D%20dated.filter(%0A%20%20%20%20%20%20%20%20pl.col(%22InvoiceDate%22)%20%3E%3D%20datetime(2011%2C%201%2C%204)%2C%0A%20%20%20%20%20%20%20%20pl.col(%22InvoiceDate%22)%20%3C%20datetime(2011%2C%201%2C%205)%2C%0A%20%20%20%20).height%0A%0A%20%20%20%20(%0A%20%20%20%20%20%20%20%20f%22equality%20against%20the%20date%3A%20%7B_exact%7D%20rows%22%2C%0A%20%20%20%20%20%20%20%20f%22comparing%20.dt.date()%3A%20%7B_by_date%3A%2C%7D%20rows%22%2C%0A%20%20%20%20%20%20%20%20f%22half-open%20window%3A%20%7B_window%3A%2C%7D%20rows%22%2C%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%22%0A%20%20%20%20The%20half-open%20form%20is%20the%20one%20to%20write.%20Take%20everything%20greater%20than%20or%20equal%20to%20the%0A%20%20%20%20first%20instant%20and%20less%20than%20the%20first%20instant%20of%20the%20next%20period.%20It%20reads%20slightly%20worse%0A%20%20%20%20than%20a%20range%20with%20two%20dates%20in%20it%20and%20it%20never%20loses%20the%20last%20day.%0A%0A%20%20%20%20%F0%9F%91%89%20**Try%20it%3A**%20ask%20for%20January%20with%0A%20%20%20%20%60.is_between(datetime(2011%2C%201%2C%201)%2C%20datetime(2011%2C%201%2C%2031))%60%2C%20then%20again%20with%0A%20%20%20%20%60%3E%3D%20datetime(2011%2C%201%2C%201)%60%20and%20%60%3C%20datetime(2011%2C%202%2C%201)%60%2C%20and%20compare%20the%20two%20counts.%0A%0A%20%20%20%20%23%23%23%20%E2%9C%94%20Check%20your%20understanding%0A%0A%20%20%20%201.%20%60order_date%5B%3A7%5D%60%20gives%20the%20right%20month%20for%20one%20way%20of%20writing%20a%20date.%20Name%20a%20file%0A%20%20%20%20%20%20%20where%20it%20gives%20a%20wrong%20answer%20and%20does%20not%20raise.%0A%20%20%20%202.%20A%20column%20parses%20cleanly%20with%20%60%22%25m%2F%25d%2F%25Y%22%60%20and%20cleanly%20with%20%60%22%25d%2F%25m%2F%25Y%22%60.%20How%20would%0A%20%20%20%20%20%20%20you%20find%20out%20which%20one%20the%20file%20meant%3F%0A%20%20%20%203.%20Your%20monthly%20revenue%20table%20shows%20a%2058%20percent%20drop%20in%20the%20final%20month.%20Name%20two%0A%20%20%20%20%20%20%20things%20to%20check%20before%20that%20goes%20in%20a%20memo.%0A%20%20%20%204.%20%60pl.col(%22InvoiceDate%22)%20%3D%3D%20datetime(2011%2C%201%2C%204)%60%20returns%20zero%20rows%20and%20the%20data%20has%0A%20%20%20%20%20%20%201%2C156%20rows%20on%20that%20day.%20What%20is%20the%20code%20comparing%3F%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%20mo.md(r%22%22%22%0A%20%20%20%20---%0A%20%20%20%20%23%23%2026.%20%60.value_counts()%60%20and%20%60.group_by().agg()%60%0A%0A%20%20%20%20Chapter%20B%20built%20a%20dictionary%20of%20running%20totals%20with%20a%20loop%20that%20did%20three%20things.%20Read%0A%20%20%20%20the%20key%2C%20make%20sure%20it%20has%20a%20slot%2C%20add%20this%20row%20to%20the%20slot.%20polars%20has%20that%20loop%20written%0A%20%20%20%20already.%20One%20form%20counts%2C%20and%20the%20other%20does%20everything%20else.%0A%0A%20%20%20%20%F0%9F%93%96%20**Used%20in%3A**%20From%20a%20CSV%20file%20to%20a%20dataframe%20%C2%B7%20Building%20and%20querying%20your%20database%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%20mo.md(r%22%22%22%0A%20%20%20%20%23%23%23%20The%20loop%20you%20already%20know%20how%20to%20write%0A%0A%20%20%20%20This%20is%20Chapter%20B%20section%2013%2C%20aimed%20at%20a%20real%20column.%20It%20is%20the%20same%20three%20steps%20and%20it%0A%20%20%20%20runs%20530%2C104%20times.%0A%0A%20%20%20%20%F0%9F%91%89%20**Try%20it%3A**%20run%20the%20same%20loop%20over%20%60dated%5B%22Month%22%5D%60%20instead.%20Predict%20how%20many%20keys%0A%20%20%20%20come%20out%20before%20you%20run%20it.%0A%20%20%20%20%22%22%22)%0A%20%20%20%20return%0A%0A%0A%40app.cell%0Adef%20_(sales)%3A%0A%20%20%20%20lines_by_country%20%3D%20%7B%7D%0A%20%20%20%20for%20country_name%20in%20sales%5B%22Country%22%5D%3A%0A%20%20%20%20%20%20%20%20if%20country_name%20not%20in%20lines_by_country%3A%0A%20%20%20%20%20%20%20%20%20%20%20%20lines_by_country%5Bcountry_name%5D%20%3D%200%0A%20%20%20%20%20%20%20%20lines_by_country%5Bcountry_name%5D%20%2B%3D%201%0A%0A%20%20%20%20_biggest%20%3D%20sorted(lines_by_country.items()%2C%20key%3Dlambda%20pair%3A%20-pair%5B1%5D)%5B%3A3%5D%0A%20%20%20%20f%22%7Blen(lines_by_country)%7D%20countries.%20Top%20three%3A%20%7B_biggest%7D%22%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%22%0A%20%20%20%20%23%23%23%20%60.value_counts()%60%20is%20that%20loop%0A%0A%20%20%20%20The%20answer%20is%20the%20same%20and%20it%20takes%20one%20call.%20It%20comes%20back%20as%20a%20frame%20with%20two%0A%20%20%20%20columns%2C%20the%20value%20and%20its%20%60count%60.%20Pass%20%60sort%3DTrue%60%20for%20the%20largest%20first%3B%20without%20it%0A%20%20%20%20the%20rows%20come%20back%20in%20an%20order%20that%20changes%20from%20one%20run%20to%20the%20next.%0A%20%20%20%20%22%22%22)%0A%20%20%20%20return%0A%0A%0A%40app.cell%0Adef%20_(sales)%3A%0A%20%20%20%20country_lines%20%3D%20sales%5B%22Country%22%5D.value_counts(sort%3DTrue)%0A%0A%20%20%20%20country_lines.head(6)%0A%20%20%20%20return%0A%0A%0A%40app.cell%0Adef%20_(pl%2C%20sales)%3A%0A%20%20%20%20_share%20%3D%20sales%5B%22Country%22%5D.value_counts(sort%3DTrue%2C%20normalize%3DTrue)%0A%20%20%20%20_uk_share%20%3D%20_share.filter(pl.col(%22Country%22)%20%3D%3D%20%22United%20Kingdom%22)%5B%22proportion%22%5D%5B0%5D%0A%20%20%20%20_missing_customer%20%3D%20sales%5B%22CustomerID%22%5D.null_count()%0A%0A%20%20%20%20(%0A%20%20%20%20%20%20%20%20f%22United%20Kingdom%20is%20%7B_uk_share%3A.1%25%7D%20of%20the%20lines%22%2C%0A%20%20%20%20%20%20%20%20f%22CustomerID%20is%20missing%20on%20%7B_missing_customer%3A%2C%7D%20rows%2C%20and%20value_counts%20counts%20those%20as%20a%20row%20of%20their%20own%2C%20labeled%20null%22%2C%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%22%0A%20%20%20%20%60normalize%3DTrue%60%20divides%20by%20the%20row%20count%20and%20gives%20shares%2C%20in%20a%20column%20called%0A%20%20%20%20%60proportion%60.%20The%20missing%20values%20get%20a%20row%20of%20their%20own%2C%20so%20the%20counts%20always%20add%20up%20to%0A%20%20%20%20the%20rows%20in%20the%20frame%2C%20and%20the%20first%20thing%20to%20read%20on%20a%20column%20you%20have%20not%20met%20is%0A%20%20%20%20whether%20that%20%60null%60%20row%20is%20there%20and%20how%20big%20it%20is.%0A%0A%20%20%20%20%F0%9F%91%89%20**Try%20it%3A**%20run%20%60.value_counts(sort%3DTrue)%60%20on%20%60Description%60%20and%20read%20the%20top%20five.%0A%20%20%20%20Then%20run%20it%20on%20%60StockCode%60%20and%20compare%20the%20two%20counts%20for%20the%20same%20product.%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%20mo.md(r%22%22%22%0A%20%20%20%20%23%23%23%20Questions%20before%20you%20write%20a%20%60group_by%60%0A%0A%20%20%20%20Answer%20these%20in%20order%20and%20the%20call%20writes%20itself.%0A%0A%20%20%20%201.%20**What%20am%20I%20grouping%20by%3F**%20The%20dimension.%20Country%2C%20month%2C%20product%2C%20customer.%0A%20%20%20%202.%20**What%20am%20I%20aggregating%3F**%20The%20measure.%20Revenue%2C%20quantity%2C%20days%20to%20ship.%0A%20%20%20%203.%20**Which%20function%3F**%20%60.sum()%60%2C%20%60.mean()%60%2C%20%60.n_unique()%60%2C%20%60.min()%60%2C%20%60.max()%60%2C%0A%20%20%20%20%20%20%20%60.median()%60%2C%20and%20%60pl.len()%60%20for%20the%20number%20of%20rows.%0A%0A%20%20%20%20Each%20keyword%20argument%20inside%20%60.agg()%60%20names%20an%20output%20column%20and%20says%20where%20it%20came%0A%20%20%20%20from%2C%20which%20is%20the%20form%20to%20write%20from%20the%20start.%20The%20SQL%20beside%20each%20line%20is%20the%20same%0A%20%20%20%20statement.%0A%20%20%20%20%22%22%22)%0A%20%20%20%20return%0A%0A%0A%40app.cell%0Adef%20_(pl%2C%20sales)%3A%0A%20%20%20%20country_summary%20%3D%20(%0A%20%20%20%20%20%20%20%20sales.group_by(%22Country%22)%20%20%20%20%20%20%20%20%20%20%20%20%20%20%20%20%20%20%20%20%20%20%20%20%20%23%20GROUP%20BY%20Country%0A%20%20%20%20%20%20%20%20.agg(%0A%20%20%20%20%20%20%20%20%20%20%20%20lines%3Dpl.len()%2C%20%20%20%20%20%20%20%20%20%20%20%20%20%20%20%20%20%20%20%20%20%20%20%20%20%20%20%20%20%20%20%23%20COUNT(*)%0A%20%20%20%20%20%20%20%20%20%20%20%20orders%3Dpl.col(%22InvoiceNo%22).n_unique()%2C%20%20%20%20%20%20%20%20%23%20COUNT(DISTINCT%20InvoiceNo)%0A%20%20%20%20%20%20%20%20%20%20%20%20revenue%3Dpl.col(%22Revenue%22).sum()%2C%20%20%20%20%20%20%20%20%20%20%20%20%20%20%23%20SUM(Quantity%20*%20UnitPrice)%0A%20%20%20%20%20%20%20%20)%0A%20%20%20%20%20%20%20%20.sort(%22revenue%22%2C%20descending%3DTrue)%20%20%20%20%20%20%20%20%20%20%20%20%20%20%20%20%20%23%20ORDER%20BY%20revenue%20DESC%0A%20%20%20%20)%0A%0A%20%20%20%20country_summary.head(8).with_columns(pl.col(%22revenue%22).round(2))%0A%20%20%20%20return%20(country_summary%2C)%0A%0A%0A%40app.cell(hide_code%3DTrue)%0Adef%20_(mo)%3A%0A%20%20%20%20mo.md(r%22%22%22%0A%20%20%20%20%60pl.len()%60%20counts%20rows%20and%20%60.n_unique()%60%20counts%20distinct%20values%2C%20so%20%60lines%60%20and%20%60orders%60%0A%20%20%20%20answer%20two%20different%20questions%20about%20the%20same%20invoices.%20Reporting%20one%20as%20the%20other%20is%20a%0A%20%20%20%20common%20way%20to%20overstate%20how%20many%20customers%20or%20orders%20a%20market%20has.%0A%0A%20%20%20%20%F0%9F%91%89%20**Try%20it%3A**%20add%20%60avg_line%3Dpl.col(%22Revenue%22).mean()%60%20and%0A%20%20%20%20%60top_line%3Dpl.col(%22Revenue%22).max()%60%20to%20the%20same%20%60.agg()%60%20call.%20Then%20group%20%60dated%60%20by%0A%20%20%20%20%60Country%60%20and%20%60Month%60%20together%20with%20%60.group_by(%22Country%22%2C%20%22Month%22)%60.%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%20mo.md(r%22%22%22%0A%20%20%20%20%23%23%23%20State%20what%20the%20result%20should%20look%20like%20before%20you%20run%20it%0A%0A%20%20%20%20The%20result%20should%20be%20one%20row%20per%20country%2C%20four%20columns%2C%20and%20a%20revenue%20column%20that%20still%0A%20%20%20%20adds%20to%20the%20same%20total.%20All%20three%20are%20checkable%2C%20and%20an%20%60assert%60%20is%20how%20you%20check%20them.%0A%20%20%20%20%22%22%22)%0A%20%20%20%20return%0A%0A%0A%40app.cell%0Adef%20_(country_summary%2C%20sales)%3A%0A%20%20%20%20assert%20country_summary.height%20%3D%3D%20sales%5B%22Country%22%5D.n_unique()%2C%20%22expected%20one%20row%20per%20country%22%0A%20%20%20%20assert%20abs(country_summary%5B%22revenue%22%5D.sum()%20-%20sales%5B%22Revenue%22%5D.sum())%20%3C%200.01%2C%20%22revenue%20changed%22%0A%0A%20%20%20%20(%0A%20%20%20%20%20%20%20%20f%22%7Bcountry_summary.height%7D%20rows%20and%20%7Bcountry_summary.width%7D%20columns%22%2C%0A%20%20%20%20%20%20%20%20f%22revenue%20still%20totals%20%7Bcountry_summary%5B'revenue'%5D.sum()%3A%2C.2f%7D%22%2C%0A%20%20%20%20)%0A%20%20%20%20return%0A%0A%0A%40app.cell%0Adef%20_(pl%2C%20sales)%3A%0A%20%20%20%20_by_customer%20%3D%20sales.group_by(%22CustomerID%22).agg(pl.col(%22Revenue%22).sum())%0A%20%20%20%20_no_customer%20%3D%20_by_customer.filter(pl.col(%22CustomerID%22).is_null())%5B%22Revenue%22%5D%5B0%5D%0A%0A%20%20%20%20(%0A%20%20%20%20%20%20%20%20f%22%7B_by_customer.height%3A%2C%7D%20customer%20groups%2C%20one%20of%20them%20null%22%2C%0A%20%20%20%20%20%20%20%20f%22the%20null%20group%20carries%20%7B_no_customer%3A%2C.2f%7D%20of%20revenue%22%2C%0A%20%20%20%20%20%20%20%20f%22which%20is%20%7B_no_customer%20%2F%20sales%5B'Revenue'%5D.sum()%3A.1%25%7D%20of%20the%20total%22%2C%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%22%0A%20%20%20%20A%20missing%20value%20in%20the%20grouping%20key%20becomes%20a%20group%20of%20its%20own%2C%20labeled%20%60null%60%2C%20so%20no%0A%20%20%20%20row%20goes%20missing%20and%20the%20grouped%20total%20still%20matches.%20Grouped%20by%20customer%2C%20that%20%60null%60%0A%20%20%20%20group%20is%20the%20largest%20customer%20in%20the%20file%20by%20a%20long%20way%2C%20and%20it%20is%20not%20a%20customer%20at%0A%20%20%20%20all.%20It%20has%20to%20be%20named%2C%20or%20dropped%2C%20before%20anybody%20ranks%20customers.%0A%0A%20%20%20%20%F0%9F%91%89%20**Try%20it%3A**%20sort%20that%20customer%20table%20by%20revenue%20and%20read%20the%20top%20five.%20Then%20drop%20the%0A%20%20%20%20%60null%60%20group%20with%20%60.drop_nulls(%22CustomerID%22)%60%20and%20read%20them%20again.%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%20mo.md(r%22%22%22%0A%20%20%20%20%23%23%23%20Groups%20come%20back%20in%20no%20fixed%20order%0A%0A%20%20%20%20%60group_by%60%20works%20on%20several%20parts%20of%20the%20frame%20at%20once%20and%20returns%20the%20groups%20as%20they%0A%20%20%20%20finish%2C%20so%20the%20same%20call%20can%20list%20the%20countries%20in%20a%20different%20order%20every%20time%20it%0A%20%20%20%20runs.%20Every%20number%20is%20right%20each%20time.%20A%20reader%20comparing%20two%20runs%2C%20or%20two%20screenshots%2C%0A%20%20%20%20sees%20two%20different%20tables.%0A%0A%20%20%20%20End%20every%20%60group_by%60%20in%20a%20%60.sort(...)%60%2C%20as%20every%20cell%20in%20this%20chapter%20does.%20When%20the%0A%20%20%20%20order%20the%20groups%20first%20appear%20in%20is%20the%20one%20you%20want%2C%20%60.group_by(%22Country%22%2C%0A%20%20%20%20maintain_order%3DTrue)%60%20keeps%20it.%0A%0A%20%20%20%20The%20grouping%20key%20stays%20an%20ordinary%20column.%20Section%2030%20shows%20the%20library%20where%20it%20does%0A%20%20%20%20not.%0A%0A%20%20%20%20%F0%9F%91%89%20**Try%20it%3A**%20delete%20the%20%60.sort(...)%60%20line%20from%20%60country_summary%60%2C%20run%20the%20cell%20three%0A%20%20%20%20times%2C%20and%20read%20the%20first%20country%20each%20time.%0A%0A%20%20%20%20%23%23%23%20%E2%9C%94%20Check%20your%20understanding%0A%0A%20%20%20%201.%20%60.value_counts()%60%20and%20a%20%60for%60%20loop%20over%20the%20same%20column%20give%20the%20same%20numbers.%20Name%0A%20%20%20%20%20%20%20one%20thing%20the%20loop%20lets%20you%20do%20that%20the%20one-liner%20does%20not.%0A%20%20%20%202.%20%60lines%60%20and%20%60orders%60%20in%20%60country_summary%60%20come%20from%20the%20same%20invoices.%20What%20is%20the%0A%20%20%20%20%20%20%20difference%20between%20them%2C%20and%20which%20one%20would%20you%20put%20in%20a%20slide%20headed%20*orders%20per%0A%20%20%20%20%20%20%20market*%3F%0A%20%20%20%203.%20Your%20per-customer%20table%20has%20a%20row%20whose%20%60CustomerID%60%20is%20%60null%60%2C%20and%20it%20is%20the%20largest%0A%20%20%20%20%20%20%20row.%20What%20is%20it%2C%20and%20what%20do%20you%20do%20with%20it%20before%20you%20rank%20customers%3F%0A%20%20%20%204.%20You%20run%20the%20same%20%60group_by%60%20twice%20and%20the%20rows%20come%20back%20in%20a%20different%20order.%20Is%0A%20%20%20%20%20%20%20either%20answer%20wrong%3F%20What%20do%20you%20add%20so%20a%20reader%20sees%20the%20same%20table%20every%20time%3F%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%20mo.md(r%22%22%22%0A%20%20%20%20---%0A%20%20%20%20%23%23%2027.%20Merging%20two%20tables%0A%0A%20%20%20%20One%20table%20rarely%20holds%20the%20whole%20question.%20%60.join()%60%20lines%20two%20frames%20up%20on%20a%20shared%0A%20%20%20%20key%20and%20hands%20back%20one%20frame.%20Two%20arguments%20do%20the%20work%3A%20%60on%3D%60%20names%20the%20key%2C%20and%20%60how%3D%60%0A%20%20%20%20decides%20which%20rows%20survive.%20pandas%20calls%20the%20same%20operation%20a%20merge.%0A%0A%20%20%20%20%F0%9F%93%96%20**Used%20in%3A**%20Combining%20tables%20with%20%60JOIN%60%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%20mo.md(r%22%22%22%0A%20%20%20%20%23%23%23%20%60on%3D%60%20names%20the%20key%20and%20%60how%3D%60%20decides%20who%20stays%0A%0A%20%20%20%20%7C%20%60how%3D%60%20%7C%20what%20comes%20back%20%7C%20SQL%20%7C%0A%20%20%20%20%7C---%7C---%7C---%7C%0A%20%20%20%20%7C%20%60%22inner%22%60%2C%20the%20default%20%7C%20rows%20whose%20key%20is%20in%20both%20frames%20%7C%20%60INNER%20JOIN%60%20%7C%0A%20%20%20%20%7C%20%60%22left%22%60%20%7C%20every%20row%20of%20the%20left%20frame%2C%20%60null%60%20where%20the%20right%20has%20no%20match%20%7C%20%60LEFT%20JOIN%60%20%7C%0A%20%20%20%20%7C%20%60%22right%22%60%20%7C%20every%20row%20of%20the%20right%20frame%20%7C%20%60RIGHT%20JOIN%60%20%7C%0A%20%20%20%20%7C%20%60%22full%22%60%20%7C%20every%20row%20of%20both%20%7C%20%60FULL%20OUTER%20JOIN%60%20%7C%0A%20%20%20%20%7C%20%60%22anti%22%60%20%7C%20the%20rows%20of%20the%20left%20frame%20that%20found%20**no**%20match%20%7C%20%60LEFT%20JOIN%20...%20WHERE%20right.key%20IS%20NULL%60%20%7C%0A%0A%20%20%20%20The%20default%20is%20%60%22inner%22%60%2C%20so%20a%20join%20written%20without%20%60how%3D%60%20throws%20away%20the%20rows%20that%0A%20%20%20%20did%20not%20match%20and%20reports%20nothing%20about%20them.%0A%0A%20%20%20%20Online%20Retail%20is%20one%20flat%20table%2C%20so%20this%20section%20works%20on%20the%20course%20warehouse%2C%20where%0A%20%20%20%20the%20keys%20resolve%20and%20one%20order%20has%20many%20lines.%0A%0A%20%20%20%20%F0%9F%91%89%20**Try%20it%3A**%20print%20%60nw_orders.columns%60%20and%20%60nw_lines.columns%60%20and%20find%20the%20one%20name%20they%0A%20%20%20%20share.%20That%20name%20is%20what%20goes%20in%20%60on%3D%60.%0A%20%20%20%20%22%22%22)%0A%20%20%20%20return%0A%0A%0A%40app.cell%0Adef%20_(northwind%2C%20nw_lines)%3A%0A%20%20%20%20nw_orders%20%3D%20northwind(%22orders%22)%0A%0A%20%20%20%20f%22%7Bnw_orders.height%3A%2C%7D%20orders%20and%20%7Bnw_lines.height%3A%2C%7D%20order%20lines%22%0A%20%20%20%20return%20(nw_orders%2C)%0A%0A%0A%40app.cell(hide_code%3DTrue)%0Adef%20_(mo)%3A%0A%20%20%20%20mo.md(r%22%22%22%0A%20%20%20%20%23%23%23%20A%20join%20can%20hand%20back%20more%20rows%20than%20either%20side%20had%0A%0A%20%20%20%20Most%20orders%20have%20several%20lines.%20Joining%20the%20two%20copies%20each%20order%20row%20once%20per%20line%2C%20so%0A%20%20%20%20the%20order-level%20columns%20arrive%20repeated.%0A%20%20%20%20%22%22%22)%0A%20%20%20%20return%0A%0A%0A%40app.cell%0Adef%20_(nw_lines%2C%20nw_orders)%3A%0A%20%20%20%20joined_lines%20%3D%20nw_orders.join(nw_lines%2C%20on%3D%22OrderID%22%2C%20how%3D%22inner%22)%0A%0A%20%20%20%20(%0A%20%20%20%20%20%20%20%20f%22%7Bnw_orders.height%3A%2C%7D%20rows%20joined%20with%20%7Bnw_lines.height%3A%2C%7D%20rows%20gives%20%7Bjoined_lines.height%3A%2C%7D%22%2C%0A%20%20%20%20%20%20%20%20f%22columns%3A%20%7Bnw_orders.width%7D%20%2B%20%7Bnw_lines.width%7D%20-%201%20key%20%3D%20%7Bjoined_lines.width%7D%22%2C%0A%20%20%20%20)%0A%20%20%20%20return%20(joined_lines%2C)%0A%0A%0A%40app.cell%0Adef%20_(joined_lines%2C%20nw_orders)%3A%0A%20%20%20%20_before%20%3D%20float(nw_orders%5B%22Freight%22%5D.sum())%0A%20%20%20%20_after%20%3D%20float(joined_lines%5B%22Freight%22%5D.sum())%0A%0A%20%20%20%20(%0A%20%20%20%20%20%20%20%20f%22freight%20before%20the%20join%3A%20%7B_before%3A%2C.2f%7D%22%2C%0A%20%20%20%20%20%20%20%20f%22freight%20after%20the%20join%3A%20%20%7B_after%3A%2C.2f%7D%22%2C%0A%20%20%20%20%20%20%20%20f%22which%20is%20%7B_after%20%2F%20_before%3A.1f%7D%20times%20larger%22%2C%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%22%0A%20%20%20%20Freight%20is%20charged%20once%20per%20order.%20After%20the%20join%20it%20appears%20once%20per%20**line**%2C%20so%20a%0A%20%20%20%20seven-line%20order%20contributes%20its%20freight%20seven%20times%20and%20%60SUM%60%20adds%20every%20copy.%20The%0A%20%20%20%20column%20is%20still%20called%20%60Freight%60%2C%20every%20individual%20value%20is%20still%20correct%2C%20and%20the%20total%0A%20%20%20%20is%2051%20times%20too%20large.%0A%0A%20%20%20%20The%20check%20is%20one%20argument%2C%20and%20it%20goes%20on%20the%20join%20itself.%20%60validate%3D%60%20states%20how%20many%0A%20%20%20%20rows%20on%20each%20side%20share%20a%20key%2C%20and%20polars%20raises%20when%20the%20data%20disagrees%3A%0A%0A%20%20%20%20%60%60%60python%0A%20%20%20%20nw_orders.join(nw_lines%2C%20on%3D%22OrderID%22%2C%20validate%3D%221%3A1%22)%0A%20%20%20%20%60%60%60%0A%0A%20%20%20%20%60%60%60%0A%20%20%20%20ComputeError%3A%20join%20keys%20did%20not%20fulfill%201%3A1%20validation%0A%20%20%20%20%60%60%60%0A%0A%20%20%20%20Here%20the%20check%20is%20supposed%20to%20fail.%20One%20order%20to%20many%20lines%20is%20%60%221%3Am%22%60%2C%20which%20passes%2C%0A%20%20%20%20and%20writing%20it%20down%20makes%20you%20specify%20which%20side%20you%20are%20counting%20on%20before%20you%20aggregate.%0A%20%20%20%20When%20the%20answer%20is%20per%20order%2C%20aggregate%20the%20many%20side%20first%20and%20join%20the%20summary.%0A%20%20%20%20%22%22%22)%0A%20%20%20%20return%0A%0A%0A%40app.cell%0Adef%20_(nw_lines%2C%20nw_orders%2C%20pl)%3A%0A%20%20%20%20_line_totals%20%3D%20(%0A%20%20%20%20%20%20%20%20nw_lines.cast(%7B%22UnitPrice%22%3A%20pl.Float64%7D)%0A%20%20%20%20%20%20%20%20.group_by(%22OrderID%22)%0A%20%20%20%20%20%20%20%20.agg(Goods%3D(pl.col(%22UnitPrice%22)%20*%20pl.col(%22Quantity%22)%20*%20(1%20-%20pl.col(%22Discount%22))).sum())%0A%20%20%20%20)%0A%20%20%20%20_per_order%20%3D%20nw_orders.join(_line_totals%2C%20on%3D%22OrderID%22%2C%20how%3D%22left%22%2C%20validate%3D%221%3A1%22)%0A%0A%20%20%20%20assert%20_per_order.height%20%3D%3D%20nw_orders.height%2C%20%22one%20row%20per%20order%22%0A%0A%20%20%20%20(%0A%20%20%20%20%20%20%20%20f%22%7B_per_order.height%3A%2C%7D%20rows%22%2C%0A%20%20%20%20%20%20%20%20f%22freight%20%7Bfloat(_per_order%5B'Freight'%5D.sum())%3A%2C.2f%7D%22%2C%0A%20%20%20%20%20%20%20%20f%22goods%20%7B_per_order%5B'Goods'%5D.sum()%3A%2C.2f%7D%22%2C%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%22%0A%20%20%20%20Freight%20is%20back%20to%20its%20real%20total%20because%20the%20join%20no%20longer%20duplicates%20orders%2C%20and%0A%20%20%20%20%60validate%3D%221%3A1%22%60%20passed%2C%20which%20is%20the%20claim%20written%20down.%0A%0A%20%20%20%20%F0%9F%91%89%20**Try%20it%3A**%20join%20the%20two%20frames%20the%20other%20way%20round%2C%20%60nw_lines.join(nw_orders%2C%20...)%60%2C%0A%20%20%20%20and%20check%20the%20row%20count.%20Then%20count%20how%20many%20lines%20the%20largest%20order%20has%20with%0A%20%20%20%20%60nw_lines%5B%22OrderID%22%5D.value_counts(sort%3DTrue).head()%60.%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%20mo.md(r%22%22%22%0A%20%20%20%20%23%23%23%20An%20inner%20join%20drops%20what%20did%20not%20match%20and%20says%20nothing%0A%0A%20%20%20%20Here%20is%20a%20lookup%20table%20of%20the%20kind%20somebody%20emails%20you%2C%20seven%20countries%20mapped%20to%20a%0A%20%20%20%20region.%20A%20left%20join%20keeps%20every%20country%20and%20leaves%20%60null%60%20where%20the%20lookup%20had%20no%0A%20%20%20%20match.%20An%20anti%20join%20lists%20the%20countries%20that%20found%20no%20match%2C%20which%20is%20what%20turns%20a%0A%20%20%20%20silent%20drop%20into%20a%20list%20you%20can%20read.%0A%20%20%20%20%22%22%22)%0A%20%20%20%20return%0A%0A%0A%40app.cell%0Adef%20_(country_summary%2C%20pl)%3A%0A%20%20%20%20region_lookup%20%3D%20pl.DataFrame(%0A%20%20%20%20%20%20%20%20%7B%0A%20%20%20%20%20%20%20%20%20%20%20%20%22Country%22%3A%20%5B%0A%20%20%20%20%20%20%20%20%20%20%20%20%20%20%20%20%22United%20Kingdom%22%2C%20%22Germany%22%2C%20%22France%22%2C%20%22Netherlands%22%2C%0A%20%20%20%20%20%20%20%20%20%20%20%20%20%20%20%20%22Spain%22%2C%20%22Belgium%22%2C%20%22Ireland%22%2C%0A%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%22Region%22%3A%20%5B%22UK%22%2C%20%22EU%22%2C%20%22EU%22%2C%20%22EU%22%2C%20%22EU%22%2C%20%22EU%22%2C%20%22EU%22%5D%2C%0A%20%20%20%20%20%20%20%20%7D%0A%20%20%20%20)%0A%0A%20%20%20%20country_regions%20%3D%20country_summary.join(region_lookup%2C%20on%3D%22Country%22%2C%20how%3D%22left%22)%0A%20%20%20%20unmatched%20%3D%20country_summary.join(region_lookup%2C%20on%3D%22Country%22%2C%20how%3D%22anti%22)%0A%0A%20%20%20%20(%0A%20%20%20%20%20%20%20%20f%22left%20join%3A%20%7Bcountry_regions.height%7D%20countries%2C%20%7Bcountry_regions%5B'Region'%5D.null_count()%7D%20with%20no%20region%22%2C%0A%20%20%20%20%20%20%20%20f%22anti%20join%3A%20%7Bunmatched.height%7D%20countries%20the%20lookup%20does%20not%20have%22%2C%0A%20%20%20%20)%0A%20%20%20%20return%20region_lookup%2C%20unmatched%0A%0A%0A%40app.cell%0Adef%20_(country_summary%2C%20region_lookup%2C%20unmatched)%3A%0A%20%20%20%20_matched%20%3D%20country_summary.join(region_lookup%2C%20on%3D%22Country%22%2C%20how%3D%22inner%22)%0A%20%20%20%20_biggest_loss%20%3D%20unmatched.sort(%22revenue%22%2C%20descending%3DTrue).row(0%2C%20named%3DTrue)%0A%0A%20%20%20%20(%0A%20%20%20%20%20%20%20%20f%22an%20inner%20join%20would%20keep%20%7B_matched.height%7D%20of%20%7Bcountry_summary.height%7D%20countries%22%2C%0A%20%20%20%20%20%20%20%20f%22and%20%7B_matched%5B'revenue'%5D.sum()%20%2F%20country_summary%5B'revenue'%5D.sum()%3A.2%25%7D%20of%20the%20revenue%22%2C%0A%20%20%20%20%20%20%20%20f%22largest%20market%20it%20would%20drop%3A%20%7B_biggest_loss%5B'Country'%5D%7D%20at%20%7B_biggest_loss%5B'revenue'%5D%3A%2C.2f%7D%22%2C%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%22%0A%20%20%20%20The%20lookup%20says%20%60Ireland%60%20and%20the%20data%20says%20%60EIRE%60.%20They%20are%20the%20same%20country%20and%20no%0A%20%20%20%20string%20comparison%20thinks%20so%2C%20so%20an%20inner%20join%20here%20removes%20the%20third%20largest%20market%20in%0A%20%20%20%20the%20file%20along%20with%2031%20smaller%20ones%2C%20and%20the%20revenue%20total%20drops%20by%207.6%20percent%20with%0A%20%20%20%20nothing%20on%20screen%20to%20say%20why.%0A%0A%20%20%20%20Three%20habits%20cover%20this%2C%20and%20all%20three%20are%20one%20line%3A%0A%0A%20%20%20%20-%20Join%20with%20%60how%3D%22left%22%60%20while%20you%20are%20working%2C%20so%20unmatched%20rows%20stay%20visible%20as%0A%20%20%20%20%20%20%60null%60.%0A%20%20%20%20-%20Run%20the%20same%20join%20with%20%60how%3D%22anti%22%60%20and%20read%20what%20did%20not%20match.%0A%20%20%20%20-%20Compare%20the%20total%20before%20and%20after.%0A%0A%20%20%20%20%F0%9F%91%89%20**Try%20it%3A**%20add%20%60%22EIRE%22%60%20to%20the%20lookup%20and%20run%20the%20joins%20again.%20Then%20turn%20the%20anti%20join%0A%20%20%20%20round%2C%20%60region_lookup.join(country_summary%2C%20on%3D%22Country%22%2C%20how%3D%22anti%22)%60%2C%20and%20see%20which%0A%20%20%20%20lookup%20row%20matched%20nothing.%0A%0A%20%20%20%20**The%20trap.**%20A%20key%20that%20matches%20on%2090%20percent%20of%20rows%20produces%20an%20answer%20that%20looks%0A%20%20%20%20entirely%20normal.%20The%2010%20percent%20that%20did%20not%20match%20are%20gone%20from%20the%20output%2C%20so%20nothing%0A%20%20%20%20in%20the%20result%20can%20tell%20you%20they%20existed.%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%20mo.md(r%22%22%22%0A%20%20%20%20%23%23%23%20When%20both%20frames%20carry%20the%20same%20column%20name%0A%0A%20%20%20%20polars%20keeps%20both%2C%20leaves%20the%20left%20one%20as%20it%20was%2C%20and%20adds%20%60_right%60%20to%20the%20right%20one.%0A%20%20%20%20%60suffix%3D%60%20chooses%20that%20ending%20yourself%2C%20so%20the%20column%20says%20what%20it%20holds.%0A%0A%20%20%20%20Northwind%20stores%20a%20product's%20list%20price%20on%20%60products%60%20and%20the%20price%20actually%20charged%20on%0A%20%20%20%20%60order-details%60%2C%20and%20both%20columns%20are%20called%20%60UnitPrice%60.%0A%20%20%20%20%22%22%22)%0A%20%20%20%20return%0A%0A%0A%40app.cell%0Adef%20_(northwind%2C%20nw_lines)%3A%0A%20%20%20%20_products%20%3D%20northwind(%22products%22)%0A%20%20%20%20_priced%20%3D%20nw_lines.join(%0A%20%20%20%20%20%20%20%20_products.select(%22ProductID%22%2C%20%22ProductName%22%2C%20%22UnitPrice%22)%2C%0A%20%20%20%20%20%20%20%20on%3D%22ProductID%22%2C%0A%20%20%20%20%20%20%20%20how%3D%22left%22%2C%0A%20%20%20%20%20%20%20%20suffix%3D%22_list%22%2C%0A%20%20%20%20)%0A%20%20%20%20_differs%20%3D%20(_priced%5B%22UnitPrice%22%5D%20!%3D%20_priced%5B%22UnitPrice_list%22%5D).sum()%0A%0A%20%20%20%20(%0A%20%20%20%20%20%20%20%20f%22columns%3A%20%7B%5Bc%20for%20c%20in%20_priced.columns%20if%20'UnitPrice'%20in%20c%5D%7D%22%2C%0A%20%20%20%20%20%20%20%20f%22%7B_differs%3A%2C%7D%20of%20%7B_priced.height%3A%2C%7D%20lines%20were%20charged%20something%20other%20than%20the%20list%20price%22%2C%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%22%0A%20%20%20%20%F0%9F%91%89%20**Try%20it%3A**%20run%20the%20same%20join%20without%20%60suffix%3D%60%20and%20read%20the%20column%20names.%20Then%0A%20%20%20%20rename%20the%20charged%20price%20to%20%60UnitPrice_charged%60%20with%20%60.rename()%60%20before%20the%20join%2C%20so%0A%20%20%20%20neither%20column%20is%20left%20with%20a%20name%20that%20could%20mean%20either.%0A%0A%20%20%20%20%23%23%23%20%E2%9C%94%20Check%20your%20understanding%0A%0A%20%20%20%201.%20You%20join%20two%20frames%20and%20the%20row%20count%20goes%20up.%20Explain%20what%20happened%20to%20somebody%20who%0A%20%20%20%20%20%20%20has%20never%20used%20polars.%0A%20%20%20%202.%20A%20freight%20total%20is%2051%20times%20too%20large%20after%20a%20join.%20Which%20side%20of%20the%20join%20should%0A%20%20%20%20%20%20%20have%20been%20aggregated%20first%2C%20and%20which%20%60validate%3D%60%20value%20would%20have%20said%20so%3F%0A%20%20%20%203.%20%60how%3D%22inner%22%60%20is%20the%20default.%20Give%20one%20question%20where%20that%20default%20gives%20you%20a%20wrong%0A%20%20%20%20%20%20%20answer%20with%20no%20warning.%0A%20%20%20%204.%20Both%20frames%20have%20a%20column%20called%20%60UnitPrice%60%20and%20you%20join%20without%20%60suffix%3D%60.%20What%20are%0A%20%20%20%20%20%20%20the%20two%20columns%20called%2C%20and%20which%20one%20came%20from%20which%20frame%3F%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%20mo.md(r%22%22%22%0A%20%20%20%20---%0A%20%20%20%20%23%23%2028.%20Wide%20and%20long%0A%0A%20%20%20%20The%20same%20numbers%20fit%20into%20two%20shapes.%20**Wide**%20gives%20each%20category%20its%20own%20column%2C%20which%0A%20%20%20%20is%20what%20a%20spreadsheet%20looks%20like%20and%20what%20a%20person%20reads%20easily.%20**Long**%20gives%20each%0A%20%20%20%20observation%20its%20own%20row%2C%20which%20is%20what%20%60group_by%60%2C%20%60join%60%2C%20every%20plotting%20library%20and%0A%20%20%20%20every%20database%20table%20want.%20%60pivot%60%20goes%20one%20way%20and%20%60unpivot%60%20goes%20the%20other.%0A%0A%20%20%20%20%F0%9F%93%96%20**Used%20in%3A**%20Building%20your%20product%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%20mo.md(r%22%22%22%0A%20%20%20%20%23%23%23%20%60pivot%60%20builds%20the%20wide%20shape%0A%0A%20%20%20%20Four%20arguments%20say%20what%20goes%20where.%20%60index%3D%60%20is%20the%20rows%2C%20%60on%3D%60%20is%20the%20column%20whose%0A%20%20%20%20values%20become%20the%20new%20columns%2C%20and%20%60values%3D%60%20is%20what%20fills%20the%20cells.%0A%20%20%20%20%60aggregate_function%3D%60%20says%20how%20to%20combine%20the%20rows%20that%20land%20in%20the%20same%20cell.%0A%20%20%20%20%22%22%22)%0A%20%20%20%20return%0A%0A%0A%40app.cell%0Adef%20_(dated%2C%20pl)%3A%0A%20%20%20%20_top_five%20%3D%20dated%5B%22Country%22%5D.value_counts(sort%3DTrue).head(5)%5B%22Country%22%5D%0A%0A%20%20%20%20revenue_wide%20%3D%20(%0A%20%20%20%20%20%20%20%20dated.filter(pl.col(%22Country%22).is_in(_top_five.implode()))%0A%20%20%20%20%20%20%20%20.pivot(%0A%20%20%20%20%20%20%20%20%20%20%20%20on%3D%22Country%22%2C%0A%20%20%20%20%20%20%20%20%20%20%20%20index%3D%22Month%22%2C%0A%20%20%20%20%20%20%20%20%20%20%20%20values%3D%22Revenue%22%2C%0A%20%20%20%20%20%20%20%20%20%20%20%20aggregate_function%3D%22sum%22%2C%0A%20%20%20%20%20%20%20%20)%0A%20%20%20%20%20%20%20%20.sort(%22Month%22)%0A%20%20%20%20%20%20%20%20.fill_null(0)%0A%20%20%20%20%20%20%20%20.with_columns(pl.exclude(%22Month%22).round(0))%0A%20%20%20%20)%0A%0A%20%20%20%20revenue_wide%0A%20%20%20%20return%20(revenue_wide%2C)%0A%0A%0A%40app.cell(hide_code%3DTrue)%0Adef%20_(mo)%3A%0A%20%20%20%20mo.md(r%22%22%22%0A%20%20%20%20The%20result%20is%2013%20rows%20by%20five%20markets%2C%20one%20cell%20per%20month%20and%20market.%20%60.fill_null(0)%60%0A%20%20%20%20fills%20any%20month%20and%20market%20with%20no%20rows%20at%20all.%20There%20are%20none%20among%20these%20five%2C%20and%0A%20%20%20%20in%20a%20smaller%20market%20there%20would%20be%2C%20which%20is%20when%20the%20choice%20matters%3A%20zero%20means%20they%0A%20%20%20%20sold%20nothing%2C%20and%20a%20blank%20means%20you%20do%20not%20know.%0A%0A%20%20%20%20%F0%9F%91%89%20**Try%20it%3A**%20change%20%60aggregate_function%3D%22sum%22%60%20to%20%60%22mean%22%60%20and%20read%20the%20table%20again.%0A%20%20%20%20Every%20number%20changes%20and%20nothing%20tells%20you%20the%20question%20changed%20with%20it.%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%20mo.md(r%22%22%22%0A%20%20%20%20%23%23%23%20Leave%20out%20%60aggregate_function%3D%60%20and%20%60pivot%60%20refuses%0A%0A%20%20%20%20With%20no%20function%20to%20combine%20them%2C%20%60pivot%60%20needs%20exactly%20one%20value%20for%20each%20row%20and%0A%20%20%20%20column%20pair.%20Hand%20it%20a%20frame%20with%20530%2C104%20rows%20across%20302%20month%20and%20country%20pairs%20and%20it%0A%20%20%20%20stops.%0A%0A%20%20%20%20%60%60%60python%0A%20%20%20%20dated.pivot(on%3D%22Country%22%2C%20index%3D%22Month%22%2C%20values%3D%22Revenue%22)%0A%20%20%20%20%60%60%60%0A%0A%20%20%20%20%60%60%60%0A%20%20%20%20ComputeError%3A%20aggregation%20'item'%20expected%20no%20or%20a%20single%20value%2C%20got%20100%2B%20values%0A%20%20%20%20%60%60%60%0A%0A%20%20%20%20The%20count%20at%20the%20end%20of%20the%20message%20can%20change%20from%20run%20to%20run%2C%20because%20it%20depends%20on%0A%20%20%20%20which%20month%20and%20country%20polars%20checks%20first.%0A%0A%20%20%20%20That%20error%20is%20useful.%20It%20is%20%60pivot%60%20refusing%20to%20guess%20what%20to%20do%20with%20the%20extra%20rows%2C%0A%20%20%20%20and%20there%20is%20no%20default%20to%20fall%20back%20on%2C%20so%20you%20have%20to%20name%20the%20function%20yourself.%0A%0A%20%20%20%20%F0%9F%91%89%20**Try%20it%3A**%20run%20that%20line%20and%20read%20the%20error.%20Then%20group%20by%20%60Month%60%20and%20%60Country%60%0A%20%20%20%20first%20so%20there%20is%20exactly%20one%20revenue%20figure%20per%20pair%2C%20and%20pivot%20the%20result%20without%0A%20%20%20%20%60aggregate_function%3D%60.%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%20mo.md(r%22%22%22%0A%20%20%20%20%23%23%23%20%60unpivot%60%20goes%20back%20to%20long%0A%0A%20%20%20%20%60index%3D%60%20names%20the%20columns%20that%20stay%20as%20they%20are.%20Everything%20else%20collapses%20into%20two%0A%20%20%20%20columns%3A%20one%20holding%20the%20old%20column%20name%2C%20one%20holding%20the%20value.%0A%20%20%20%20%22%22%22)%0A%20%20%20%20return%0A%0A%0A%40app.cell%0Adef%20_(revenue_wide)%3A%0A%20%20%20%20revenue_long%20%3D%20revenue_wide.unpivot(%0A%20%20%20%20%20%20%20%20index%3D%22Month%22%2C%20variable_name%3D%22Country%22%2C%20value_name%3D%22Revenue%22%0A%20%20%20%20)%0A%0A%20%20%20%20(%0A%20%20%20%20%20%20%20%20f%22wide%3A%20%7Brevenue_wide.height%7D%20rows%20by%20%7Brevenue_wide.width%7D%20columns%22%2C%0A%20%20%20%20%20%20%20%20f%22long%3A%20%7Brevenue_long.height%7D%20rows%20by%20%7Brevenue_long.width%7D%20columns%22%2C%0A%20%20%20%20%20%20%20%20revenue_long.head(3)%2C%0A%20%20%20%20)%0A%20%20%20%20return%20(revenue_long%2C)%0A%0A%0A%40app.cell(hide_code%3DTrue)%0Adef%20_(mo)%3A%0A%20%20%20%20mo.md(r%22%22%22%0A%20%20%20%20A%20table%20of%2013%20by%20six%2C%20the%20months%20and%20five%20markets%2C%20becomes%2065%20by%20three%2C%20because%2013%0A%20%20%20%20times%205%20is%2065.%20No%20number%20changed%20and%20every%20one%20of%20them%20is%20now%20findable%20by%20asking%20for%20a%0A%20%20%20%20month%20and%20a%20country%20instead%20of%20by%20counting%20across%20a%20header%20row.%0A%0A%20%20%20%20This%20is%20the%20direction%20most%20business%20data%20has%20to%20travel.%20A%20monthly%20report%20with%20one%20column%0A%20%20%20%20per%20month%20cannot%20be%20grouped%2C%20cannot%20be%20filtered%20to%20a%20date%20range%2C%20and%20cannot%20be%20plotted%0A%20%20%20%20without%20being%20unpivoted%20first.%0A%0A%20%20%20%20%F0%9F%91%89%20**Try%20it%3A**%20unpivot%20%60revenue_wide%60%20with%20%60index%3D%60%20left%20out%20entirely%20and%20see%20what%0A%20%20%20%20happens%20to%20%60Month%60.%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%20mo.md(r%22%22%22%0A%20%20%20%20%23%23%23%20Which%20shape%20to%20hold%0A%0A%20%20%20%20Hold%20it%20long%20for%20anything%20the%20computer%20does%20and%20wide%20for%20anything%20a%20person%20reads.%0A%0A%20%20%20%20%7C%20you%20want%20to%20%7C%20hold%20it%20%7C%0A%20%20%20%20%7C---%7C---%7C%0A%20%20%20%20%7C%20group%2C%20filter%2C%20join%2C%20or%20plot%20%7C%20long%20%7C%0A%20%20%20%20%7C%20add%20a%20row%20total%20or%20a%20column%20total%20%7C%20wide%20%7C%0A%20%20%20%20%7C%20put%20it%20in%20a%20memo%20or%20a%20slide%20%7C%20wide%20%7C%0A%20%20%20%20%7C%20store%20it%20in%20a%20database%20table%20%7C%20long%20%7C%0A%0A%20%20%20%20A%20round%20trip%20is%20cheap.%20Unpivot%20to%20compute%2C%20pivot%20to%20present%2C%20and%20keep%20the%20long%20version%20as%0A%20%20%20%20the%20one%20you%20trust.%0A%20%20%20%20%22%22%22)%0A%20%20%20%20return%0A%0A%0A%40app.cell%0Adef%20_(pl%2C%20revenue_long%2C%20revenue_wide)%3A%0A%20%20%20%20_from_long%20%3D%20dict(%0A%20%20%20%20%20%20%20%20revenue_long.group_by(%22Country%22).agg(pl.col(%22Revenue%22).sum()).iter_rows()%0A%20%20%20%20)%0A%20%20%20%20_from_wide%20%3D%20revenue_wide.select(pl.exclude(%22Month%22).sum()).row(0%2C%20named%3DTrue)%0A%0A%20%20%20%20(%0A%20%20%20%20%20%20%20%20%7Bcountry%3A%20round(total)%20for%20country%2C%20total%20in%20sorted(_from_long.items()%2C%20key%3Dlambda%20pair%3A%20-pair%5B1%5D)%7D%2C%0A%20%20%20%20%20%20%20%20f%22the%20wide%20version%20agrees%3A%20%7Ball(abs(_from_long%5Bc%5D%20-%20_from_wide%5Bc%5D)%20%3C%200.01%20for%20c%20in%20_from_wide)%7D%22%2C%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%22%0A%20%20%20%20%F0%9F%91%89%20**Try%20it%3A**%20add%20a%20%60Total%60%20column%20to%20%60revenue_wide%60%20with%0A%20%20%20%20%60.with_columns(Total%3Dpl.sum_horizontal(pl.exclude(%22Month%22)))%60%2C%20then%20unpivot%20the%20result%0A%20%20%20%20and%20explain%20why%20%60Total%60%20now%20appears%20as%20a%20country.%0A%0A%20%20%20%20%23%23%23%20%E2%9C%94%20Check%20your%20understanding%0A%0A%20%20%20%201.%20A%20colleague%20sends%20a%20sheet%20with%20a%20column%20per%20month.%20You%20need%20revenue%20for%20the%20third%0A%20%20%20%20%20%20%20quarter.%20Which%20shape%20are%20you%20in%2C%20and%20which%20one%20do%20you%20need%3F%0A%20%20%20%202.%20%60pivot%60%20raises%20without%20%60aggregate_function%3D%60%20and%20returns%20a%20table%20with%20it.%20What%20is%0A%20%20%20%20%20%20%20the%20function%20doing%20that%20%60pivot%60%20refused%20to%20do%20on%20its%20own%3F%0A%20%20%20%203.%20%60.fill_null(0)%60%20fills%20a%20month%20and%20country%20combination%20that%20had%20no%20rows%20at%20all.%20Name%20a%0A%20%20%20%20%20%20%20case%20where%20zero%20is%20the%20wrong%20thing%20to%20put%20there.%0A%20%20%20%204.%20A%20wide%20table%20has%20a%20%60Month%60%20column%20and%20five%20market%20columns%20over%2013%20rows.%20How%20many%20rows%0A%20%20%20%20%20%20%20does%20it%20have%20after%20unpivoting%2C%20and%20where%20did%20the%20column%20headers%20go%3F%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%20mo.md(r%22%22%22%0A%20%20%20%20---%0A%20%20%20%20%23%23%2029.%20A%20chart%20with%20labeled%20axes%0A%0A%20%20%20%20A%20chart%20makes%20a%20claim%20about%20the%20data.%20It%20makes%20one%20whether%20or%20not%20you%20chose%20it%2C%20which%20is%0A%20%20%20%20why%20a%20chart%20that%20runs%20is%20only%20half%20done.%20This%20section%20draws%20the%20same%20numbers%20twice%20and%0A%20%20%20%20the%20second%20one%20is%20usable.%0A%0A%20%20%20%20%F0%9F%93%96%20**Used%20in%3A**%20From%20a%20CSV%20file%20to%20a%20dataframe%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%20mo.md(r%22%22%22%0A%20%20%20%20%23%23%23%20The%20default%20chart%0A%0A%20%20%20%20%60monthly_revenue.plot.line(x%3D%22Month%22%2C%20y%3D%22Revenue%22)%60%20is%20polars'%20own%20plotting%20call%2C%20and%0A%20%20%20%20one%20call%20on%20a%20frame%20is%20the%20whole%20chart.%20It%20draws%20with%20a%20library%20called%20**altair**.%0A%20%20%20%20Wrap%20the%20result%20in%20%60mo.ui.altair_chart()%60%20and%20marimo%20renders%20it%3B%20the%20wrap%20also%20copes%20with%0A%20%20%20%20frames%20of%20any%20size%2C%20which%20a%20bare%20altair%20chart%20does%20not.%0A%0A%20%20%20%20Here%20is%20the%20monthly%20revenue%20from%20section%2025%2C%20drawn%20with%20that%20one%20call.%0A%20%20%20%20%22%22%22)%0A%20%20%20%20return%0A%0A%0A%40app.cell%0Adef%20_(mo%2C%20monthly_revenue)%3A%0A%20%20%20%20mo.ui.altair_chart(monthly_revenue.plot.line(x%3D%22Month%22%2C%20y%3D%22Revenue%22))%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%22%0A%20%20%20%20Every%20number%20in%20it%20is%20correct%2C%20and%20the%20scale%20is%20readable.%20A%20reader%20still%20cannot%20tell%0A%20%20%20%20what%20currency%20the%20vertical%20axis%20is%20in%2C%20or%20what%20the%20chart%20is%20claiming.%20The%20two%20labels%20it%0A%20%20%20%20does%20carry%2C%20%60Month%60%20and%20%60Revenue%60%2C%20altair%20took%20from%20the%20column%20names%2C%20and%20a%20column%20name%0A%20%20%20%20is%20not%20a%20label%20somebody%20wrote%20for%20a%20reader.%0A%0A%20%20%20%20%F0%9F%91%89%20**Try%20it%3A**%20add%20%60.properties(title%3D%22Revenue%22)%60%20to%20the%20chart%20inside%20the%20wrap%20and%20run%20it%0A%20%20%20%20again.%20One%20line%20changed%2C%20and%20list%20what%20a%20reader%20still%20cannot%20tell.%0A%0A%20%20%20%20The%20months%20in%20%60monthly_revenue%60%20are%20text%2C%20such%20as%20%60%222011-06%22%60%2C%20so%20altair%20places%20them%0A%20%20%20%20evenly%20along%20the%20axis%2C%20the%20way%20it%20would%20place%20country%20names.%20That%20is%20correct%20here%20only%0A%20%20%20%20because%20no%20month%20is%20missing.%0A%0A%20%20%20%20%F0%9F%91%89%20**Try%20it%3A**%20draw%20the%20chart%20from%20%60monthly_revenue.filter(pl.col(%22Month%22)%20!%3D%20%222011-06%22)%60.%0A%20%20%20%20May%20now%20runs%20straight%20into%20July%20with%20no%20sign%20that%20June%20is%20missing.%20Then%20add%0A%20%20%20%20%60.with_columns(pl.col(%22Month%22).str.to_date(%22%25Y-%25m%22))%60%20before%20%60.plot.line(...)%60%20and%20draw%0A%20%20%20%20it%20again.%20The%20axis%20now%20spaces%20the%20months%20by%20date%2C%20and%20the%20step%20from%20May%20to%20July%20is%20twice%0A%20%20%20%20as%20wide%20as%20the%20others.%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%20mo.md(r%22%22%22%0A%20%20%20%20%23%23%23%20What%20a%20chart%20carries%0A%0A%20%20%20%201.%20**A%20title%20that%20states%20the%20finding.**%20Write%20*November%202011%20is%20the%20peak%20month*.%20A%20title%0A%20%20%20%20%20%20%20reading%20*Revenue%20by%20month*%20names%20the%20topic%20and%20leaves%20the%20reader%20to%20find%20the%20finding.%0A%20%20%20%202.%20**Both%20axes%20named%2C%20with%20units.**%20*Revenue%20(GBP)*%20says%20what%20the%20numbers%20are.%20*Revenue*%0A%20%20%20%20%20%20%20on%20its%20own%20leaves%20the%20currency%20to%20be%20guessed.%0A%20%20%20%203.%20**Scales%20a%20person%20can%20read.**%201%2C509%2C496%20instead%20of%20%601509496.33%60%2C%20and%2092%25%20instead%20of%0A%20%20%20%20%20%20%20%600.9236%60.%0A%20%20%20%204.%20**A%20legend%20as%20soon%20as%20there%20is%20more%20than%20one%20series.**%20%60color%3D%60%20adds%20one.%0A%0A%20%20%20%20Each%20one%20is%20a%20separate%20argument%2C%20which%20is%20what%20makes%20the%20list%20checkable%20line%20by%20line.%0A%20%20%20%20%60alt.X()%60%20and%20%60alt.Y()%60%20hold%20an%20axis's%20column%2C%20its%20%60title%3D%60%20and%20its%20number%20%60format%3D%60%2C%0A%20%20%20%20and%20%60.properties(title%3D...)%60%20holds%20the%20chart's.%0A%0A%20%20%20%20The%20chart%20below%20adds%20a%20fifth%20thing%20that%20is%20specific%20to%20this%20data.%20The%20last%20point%20covers%0A%20%20%20%20nine%20days%2C%20so%20the%20drop%20is%20the%20file%20ending%2C%20and%20the%20chart%20says%20so.%20That%20note%20is%20a%20second%0A%20%20%20%20chart%20drawn%20on%20top%20of%20the%20first%2C%20which%20is%20why%20this%20cell%20writes%20the%20long%20form%2C%0A%20%20%20%20%60alt.Chart(frame).mark_line()%60%2C%20that%20%60.plot.line()%60%20is%20a%20shortcut%20for%2C%20and%20joins%20the%20two%0A%20%20%20%20with%20%60%2B%60.%0A%20%20%20%20%22%22%22)%0A%20%20%20%20return%0A%0A%0A%40app.cell%0Adef%20_(alt%2C%20dated%2C%20mo%2C%20monthly_revenue%2C%20pl)%3A%0A%20%20%20%20_last%20%3D%20dated.filter(pl.col(%22Month%22)%20%3D%3D%20%222011-12%22)%5B%22InvoiceDate%22%5D.max().date()%0A%0A%20%20%20%20_line%20%3D%20(%0A%20%20%20%20%20%20%20%20alt.Chart(monthly_revenue)%0A%20%20%20%20%20%20%20%20.mark_line(point%3DTrue%2C%20color%3D%22%23528cc4%22)%0A%20%20%20%20%20%20%20%20.encode(%0A%20%20%20%20%20%20%20%20%20%20%20%20x%3Dalt.X(%22Month%22%2C%20title%3D%22Invoice%20month%22)%2C%0A%20%20%20%20%20%20%20%20%20%20%20%20y%3Dalt.Y(%22Revenue%22%2C%20title%3D%22Revenue%20(GBP)%22%2C%20axis%3Dalt.Axis(format%3D%22%2C.0f%22))%2C%0A%20%20%20%20%20%20%20%20)%0A%20%20%20%20%20%20%20%20.properties(title%3D%22November%202011%20is%20the%20peak%20month%20for%20revenue%22%2C%20width%3D600)%0A%20%20%20%20)%0A%20%20%20%20_note%20%3D%20(%0A%20%20%20%20%20%20%20%20alt.Chart(monthly_revenue.tail(1))%0A%20%20%20%20%20%20%20%20.mark_text(align%3D%22right%22%2C%20dx%3D-10%2C%20dy%3D-10%2C%20color%3D%22%23cd6b6b%22%2C%20fontWeight%3D%22bold%22)%0A%20%20%20%20%20%20%20%20.encode(x%3D%22Month%22%2C%20y%3D%22Revenue%22%2C%20text%3Dalt.value(f%22partial%20month%2C%20data%20stops%20%7B_last%7D%22))%0A%20%20%20%20)%0A%0A%20%20%20%20mo.ui.altair_chart(_line%20%2B%20_note)%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%22%0A%20%20%20%20%F0%9F%91%89%20**Try%20it%3A**%20remove%20%60%2B%20_note%60%20and%20show%20both%20charts%20to%20somebody%20who%20has%20never%20seen%20this%0A%20%20%20%20data.%20Ask%20each%20of%20them%20what%20happened%20in%20December.%0A%0A%20%20%20%20The%20%60...%60%20menu%20at%20the%20top%20right%20of%20every%20chart%20saves%20it%20as%20a%20PNG%20or%20an%20SVG%20file.%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%20mo.md(r%22%22%22%0A%20%20%20%20%23%23%23%20Aggregate%20before%20you%20plot%0A%0A%20%20%20%20The%20most%20common%20failure%20in%20agent-written%20charts%20is%20plotting%20the%20rows%20instead%20of%20the%0A%20%20%20%20answer%2C%20which%20gives%20530%2C104%20bars%20where%20five%20were%20wanted.%20Group%20first%2C%20then%20plot%20the%0A%20%20%20%20grouped%20frame.%0A%0A%20%20%20%20The%20second%20decision%20is%20what%20to%20leave%20out.%20The%20United%20Kingdom%20is%2091.5%20percent%20of%20these%0A%20%20%20%20lines%2C%20so%20a%20bar%20chart%20of%20all%2038%20countries%20is%20one%20bar%20and%2037%20slivers.%20Dropping%20it%20makes%0A%20%20%20%20the%20other%20markets%20readable%2C%20and%20the%20title%20has%20to%20say%20so.%0A%20%20%20%20%22%22%22)%0A%20%20%20%20return%0A%0A%0A%40app.cell%0Adef%20_(alt%2C%20country_summary%2C%20mo%2C%20pl)%3A%0A%20%20%20%20_export%20%3D%20(%0A%20%20%20%20%20%20%20%20country_summary.filter(pl.col(%22Country%22)%20!%3D%20%22United%20Kingdom%22)%0A%20%20%20%20%20%20%20%20.sort(%22revenue%22%2C%20descending%3DTrue)%0A%20%20%20%20%20%20%20%20.head(8)%0A%20%20%20%20%20%20%20%20.with_columns(pl.col(%22revenue%22).round(2))%0A%20%20%20%20)%0A%0A%20%20%20%20mo.ui.altair_chart(%0A%20%20%20%20%20%20%20%20alt.Chart(_export)%0A%20%20%20%20%20%20%20%20.mark_bar(color%3D%22%23528cc4%22)%0A%20%20%20%20%20%20%20%20.encode(%0A%20%20%20%20%20%20%20%20%20%20%20%20x%3Dalt.X(%0A%20%20%20%20%20%20%20%20%20%20%20%20%20%20%20%20%22revenue%22%2C%0A%20%20%20%20%20%20%20%20%20%20%20%20%20%20%20%20title%3D%22Revenue%20(GBP)%2C%20December%202010%20to%20December%202011%22%2C%0A%20%20%20%20%20%20%20%20%20%20%20%20%20%20%20%20axis%3Dalt.Axis(format%3D%22%2C.0f%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%20y%3Dalt.Y(%22Country%22%2C%20sort%3D%22-x%22%2C%20title%3D%22Market%22)%2C%0A%20%20%20%20%20%20%20%20)%0A%20%20%20%20%20%20%20%20.properties(title%3D%22Outside%20the%20UK%2C%20the%20Netherlands%20and%20EIRE%20lead%20on%20revenue%22%2C%20width%3D600)%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%22%0A%20%20%20%20%60sort%3D%22-x%22%60%20orders%20the%20bars%20by%20their%20length%2C%20longest%20first%2C%20which%20is%20what%20makes%20a%20bar%0A%20%20%20%20chart%20readable%2C%20and%20horizontal%20bars%20put%20the%20market%20names%20on%20the%20left%20where%20they%20fit%0A%20%20%20%20without%20rotating.%0A%0A%20%20%20%20%F0%9F%91%89%20**Try%20it%3A**%20put%20the%20United%20Kingdom%20back%20in%20and%20redraw.%20Then%20draw%20the%20same%20eight%0A%20%20%20%20markets%20as%20a%20share%20of%20the%20total%20instead%20of%20an%20amount%2C%20and%20see%20which%20chart%20you%20would%20put%0A%20%20%20%20in%20front%20of%20a%20manager.%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%20mo.md(r%22%22%22%0A%20%20%20%20%23%23%23%20Reading%20a%20matplotlib%20chart%0A%0A%20%20%20%20**matplotlib**%20is%20Python's%20oldest%20chart%20library%2C%20and%20pandas'%20%60.plot()%60%20draws%20with%20it.%0A%20%20%20%20You%20will%20read%20far%20more%20of%20it%20than%20you%20write.%20Here%20is%20the%20monthly%20chart%20again%20in%0A%20%20%20%20matplotlib%2C%20so%20you%20can%20recognize%20one%3A%20%60plt.%60%20or%20%60ax.%60%20at%20the%20start%20of%20every%20line%2C%20and%20a%0A%20%20%20%20%60set_%60%20call%20for%20each%20item%20in%20the%20list%20above.%0A%20%20%20%20%22%22%22)%0A%20%20%20%20return%0A%0A%0A%40app.cell%0Adef%20_(monthly_revenue)%3A%0A%20%20%20%20import%20matplotlib.pyplot%20as%20plt%0A%20%20%20%20import%20matplotlib.ticker%20as%20mticker%0A%0A%20%20%20%20_fig%2C%20_ax%20%3D%20plt.subplots(figsize%3D(8%2C%204))%0A%20%20%20%20_ax.plot(monthly_revenue%5B%22Month%22%5D%2C%20monthly_revenue%5B%22Revenue%22%5D%2C%20marker%3D%22o%22%2C%20color%3D%22%23528cc4%22)%0A%20%20%20%20_ax.set_title(%22November%202011%20is%20the%20peak%20month%20for%20revenue%22)%0A%20%20%20%20_ax.set_xlabel(%22Invoice%20month%22)%0A%20%20%20%20_ax.set_ylabel(%22Revenue%20(GBP)%22)%0A%20%20%20%20_ax.yaxis.set_major_formatter(mticker.FuncFormatter(lambda%20value%2C%20_pos%3A%20f%22%7Bvalue%3A%2C.0f%7D%22))%0A%20%20%20%20_ax.tick_params(axis%3D%22x%22%2C%20rotation%3D45)%0A%20%20%20%20_fig.tight_layout()%0A%0A%20%20%20%20_ax%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%22%0A%20%20%20%20%7C%20the%20chart%20carries%20%7C%20altair%20%7C%20matplotlib%20%7C%0A%20%20%20%20%7C---%7C---%7C---%7C%0A%20%20%20%20%7C%20a%20title%20%7C%20%60.properties(title%3D...)%60%20%7C%20%60ax.set_title(...)%60%20%7C%0A%20%20%20%20%7C%20axis%20names%20%7C%20%60alt.X(...%2C%20title%3D...)%60%20%7C%20%60ax.set_xlabel(...)%60%20%7C%0A%20%20%20%20%7C%20a%20readable%20scale%20%7C%20%60alt.Axis(format%3D%22%2C.0f%22)%60%20%7C%20%60ax.yaxis.set_major_formatter(...)%60%20%7C%0A%20%20%20%20%7C%20a%20legend%20%7C%20%60color%3D%60%20%7C%20%60label%3D%60%20on%20each%20line%2C%20then%20%60ax.legend()%60%20%7C%0A%0A%20%20%20%20In%20a%20running%20notebook%2C%20marimo%20hands%20back%20what%20the%20reader%20selected%20on%20a%20chart%20wrapped%20in%0A%20%20%20%20%60mo.ui.altair_chart%60%2C%20%60mo.ui.plotly%60%20or%20%60mo.ui.matplotlib%60%2C%20and%20altair%20supports%20this%20on%0A%20%20%20%20the%20most%20chart%20types.%20plotly%20and%20seaborn%20are%20two%20more%20libraries%20you%20will%20see%2C%20and%20the%0A%20%20%20%20same%20list%20applies%20to%20them%20unchanged.%0A%0A%20%20%20%20**The%20trap.**%20Every%20one%20of%20these%20charts%20renders%20without%20an%20error%2C%20including%20the%20first%0A%20%20%20%20one.%20Nothing%20in%20any%20chart%20library%20checks%20whether%20the%20axis%20is%20labeled%2C%20whether%20the%0A%20%20%20%20categories%20were%20aggregated%2C%20or%20whether%20the%20final%20period%20is%20complete.%20A%20person%20reads%0A%20%20%20%20those%20back%20off%20the%20picture%2C%20against%20the%20question%20that%20was%20asked.%0A%0A%20%20%20%20%23%23%23%20Which%20tool%20for%20which%20job%0A%0A%20%20%20%20%3E%20**Advanced**%0A%0A%20%20%20%20This%20course%20draws%20its%20charts%20with%20polars'%20%60.plot%60%20and%20altair%2C%20and%20nothing%20beyond%20that%20is%0A%20%20%20%20required.%20When%20a%20project%20needs%20something%20else%2C%20choose%20the%20tool%20by%20the%20job%3A%0A%0A%20%20%20%20%7C%20the%20job%20%7C%20the%20tool%20%7C%20where%20to%20start%20%7C%0A%20%20%20%20%7C---%7C---%7C---%7C%0A%20%20%20%20%7C%20a%20chart%20while%20you%20explore%2C%20or%20in%20work%20you%20hand%20in%20%7C%20%60.plot%60%2C%20which%20draws%20with%20altair%2C%20inside%20%60mo.ui.altair_chart%60%20%7C%20this%20section%2C%20then%20%5Baltair's%20gallery%5D(https%3A%2F%2Faltair-viz.github.io%2Fgallery%2F)%20for%20other%20chart%20types%20%7C%0A%20%20%20%20%7C%20a%20chart%20whose%20selection%20feeds%20the%20next%20cell%20%7C%20%60mo.ui.altair_chart%60%2C%20whose%20%60.value%60%20holds%20the%20rows%20the%20reader%20selected%20%7C%20%5Bmarimo's%20plotting%20guide%5D(https%3A%2F%2Fdocs.marimo.io%2Fguides%2Fworking_with_data%2Fplotting%2F)%20%7C%0A%20%20%20%20%7C%20a%20formatted%20table%20in%20a%20memo%2C%20with%20currency%2C%20a%20title%20and%20footnotes%20%7C%20Great%20Tables%2C%20through%20%60df.style%60%2C%20after%20%60uv%20add%20great-tables%60%20%7C%20%5BGreat%20Tables%5D(https%3A%2F%2Fposit-dev.github.io%2Fgreat-tables%2F)%20%7C%0A%20%20%20%20%7C%20a%20map%20or%20a%203D%20chart%20%7C%20plotly%2C%20after%20%60uv%20add%20%22plotly%5Bexpress%5D%22%60%20%7C%20%5BPlotly%20Express%5D(https%3A%2F%2Fplotly.com%2Fpython%2Fplotly-express%2F)%20%7C%0A%20%20%20%20%7C%20a%20statistical%20chart%20in%20one%20call%2C%20such%20as%20a%20distribution%20or%20a%20regression%20line%20%7C%20seaborn%2C%20after%20%60uv%20add%20seaborn%20pyarrow%60%3B%20it%20draws%20with%20matplotlib%20and%20converts%20the%20frame%20to%20pandas%20first%20%7C%20%5Bseaborn%5D(https%3A%2F%2Fseaborn.pydata.org%2F)%20%7C%0A%20%20%20%20%7C%20a%20chart%20another%20library%20hands%20you%20%7C%20matplotlib%2C%20read%20as%20in%20the%20section%20above%20%7C%20%5Bmatplotlib%5D(https%3A%2F%2Fmatplotlib.org%2F)%20%7C%0A%20%20%20%20%7C%20a%20chart%20somebody%20outside%20this%20course%20can%20open%20%7C%20the%20notebook%20itself%2C%20published%20%7C%20%5BPublishing%20a%20Notebook%5D(%2Fguides%2Fpublishing-a-notebook%2F)%20%7C%0A%0A%20%20%20%20Full%20reference%3A%20%5Bpolars'%20visualization%20page%5D(https%3A%2F%2Fdocs.pola.rs%2Fuser-guide%2Fmisc%2Fvisualization%2F).%0A%0A%20%20%20%20%23%23%23%20%E2%9C%94%20Check%20your%20understanding%0A%0A%20%20%20%201.%20A%20chart%20of%20monthly%20revenue%20shows%20a%20steep%20fall%20in%20the%20final%20month.%20Name%20two%20reasons%0A%20%20%20%20%20%20%20that%20would%20come%20from%20the%20file%20itself%20and%20say%20nothing%20about%20the%20business.%0A%20%20%20%202.%20%60.plot.line()%60%20and%20%60alt.Chart(...).mark_line()%60%20draw%20the%20same%20chart.%20When%20do%20you%20need%0A%20%20%20%20%20%20%20the%20second%20form%3F%0A%20%20%20%203.%20An%20agent%20hands%20you%20a%20bar%20chart%20with%20530%2C104%20bars.%20What%20was%20missing%20from%20the%20request%3F%0A%20%20%20%204.%20Give%20a%20title%20for%20the%20bar%20chart%20above%20that%20would%20be%20worse%2C%20and%20explain%20what%20makes%20it%0A%20%20%20%20%20%20%20worse.%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%20mo.md(r%22%22%22%0A%20%20%20%20---%0A%20%20%20%20%23%23%2030.%20pandas%0A%0A%20%20%20%20%3E%20**Advanced**%0A%0A%20%20%20%20Everything%20above%20is%20polars.%20**pandas**%20is%20the%20older%20and%20more%20widely%20used%20dataframe%0A%20%20%20%20library%2C%20and%20you%20will%20be%20handed%20its%20frames%3A%20by%20a%20library%20such%20as%20yfinance%2C%20by%20code%0A%20%20%20%20somebody%20else%20wrote%2C%20by%20an%20agent%20working%20outside%20marimo.%20This%20section%20teaches%20you%20to%0A%20%20%20%20recognize%20a%20pandas%20frame%20and%20convert%20it.%0A%0A%20%20%20%20%F0%9F%93%96%20**Used%20in%3A**%20Building%20and%20querying%20your%20database%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%20mo.md(r%22%22%22%0A%20%20%20%20%23%23%23%20Where%20a%20pandas%20frame%20comes%20from%0A%0A%20%20%20%20-%20**A%20library%20that%20returns%20one.**%20%60yf.Ticker(%22AAPL%22).history(period%3D%221mo%22)%60%20hands%20back%0A%20%20%20%20%20%20pandas%2C%20and%20so%20do%20most%20libraries%20older%20than%20polars.%0A%20%20%20%20-%20**A%20notebook%20somebody%20else%20wrote.**%20marimo's%20%60mo.sql%60%20returns%20pandas%20when%20the%20top%20of%0A%20%20%20%20%20%20the%20file%20says%20%60sql_output%3D%22pandas%22%60.%20Every%20notebook%20in%20this%20course%20says%20%60%22polars%22%60.%0A%20%20%20%20-%20**DuckDB's%20%60.df()%60**%2C%20where%20%60.pl()%60%20would%20have%20given%20polars%20(section%2023).%0A%20%20%20%20-%20**Code%20an%20agent%20wrote**%2C%20anywhere%20it%20was%20not%20told%20which%20library%20to%20use.%0A%0A%20%20%20%20%F0%9F%91%89%20**Try%20it%3A**%20open%20a%20marimo%20notebook%20you%20did%20not%20write%2C%20search%20the%20file%20for%0A%20%20%20%20%60sql_output%60%2C%20and%20state%20what%20its%20SQL%20cells%20hand%20back.%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%20mo.md(r%22%22%22%0A%20%20%20%20%23%23%23%20Ask%20the%20frame%20what%20it%20is%0A%0A%20%20%20%20Both%20libraries%20render%20through%20marimo's%20own%20table%2C%20so%20on%20the%20page%20they%20look%20the%20same.%0A%20%20%20%20%60type()%60%20is%20the%20habit.%20It%20costs%20one%20line%20and%20it%20is%20the%20only%20thing%20that%20answers%20the%0A%20%20%20%20question.%0A%20%20%20%20%22%22%22)%0A%20%20%20%20return%0A%0A%0A%40app.cell%0Adef%20_(country_summary%2C%20pl)%3A%0A%20%20%20%20frame_pd%20%3D%20country_summary.with_columns(pl.col(%22revenue%22).round(2)).to_pandas()%0A%0A%20%20%20%20(%0A%20%20%20%20%20%20%20%20f%22%7Btype(frame_pd).__module__%7D.%7Btype(frame_pd).__name__%7D%22%2C%0A%20%20%20%20%20%20%20%20f%22shape%20%7Bframe_pd.shape%7D%2C%20columns%20%7Blist(frame_pd.columns)%7D%22%2C%0A%20%20%20%20)%0A%20%20%20%20return%20(frame_pd%2C)%0A%0A%0A%40app.cell%0Adef%20_(frame_pd)%3A%0A%20%20%20%20frame_pd.head(3)%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%22%0A%20%20%20%20That%20table%20looks%20like%20every%20other%20table%20in%20this%20chapter%2C%20and%20%60.shape%60%2C%20%60.columns%60%20and%0A%20%20%20%20%60.head()%60%20all%20worked%2C%20which%20is%20what%20makes%20it%20hard%20to%20notice.%0A%0A%20%20%20%20%F0%9F%91%89%20**Try%20it%3A**%20print%20%60type(df)%60%20at%20the%20top%20of%20any%20notebook%20you%20did%20not%20write%2C%20before%20you%0A%20%20%20%20write%20anything%20else.%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%20mo.md(r%22%22%22%0A%20%20%20%20%23%23%23%20The%20index%0A%0A%20%20%20%20A%20pandas%20frame%20carries%20an%20**index**%3A%20a%20label%20for%20each%20row%2C%20printed%20down%20the%20left%2C%20which%0A%20%20%20%20is%20not%20a%20column.%20Most%20of%20the%20time%20it%20is%200%2C%201%2C%202%20and%20you%20can%20ignore%20it.%20A%20%60groupby%60%20puts%0A%20%20%20%20the%20grouping%20key%20there%2C%20out%20of%20the%20columns%2C%20so%20asking%20for%20the%20key%20by%20name%20fails.%0A%20%20%20%20%22%22%22)%0A%20%20%20%20return%0A%0A%0A%40app.cell%0Adef%20_(region_lookup)%3A%0A%20%20%20%20_lookup_pd%20%3D%20region_lookup.to_pandas()%0A%20%20%20%20_grouped%20%3D%20_lookup_pd.groupby(%22Region%22).agg(countries%3D(%22Country%22%2C%20%22size%22))%0A%0A%20%20%20%20(%0A%20%20%20%20%20%20%20%20f%22columns%20after%20groupby%3A%20%7Blist(_grouped.columns)%7D%22%2C%0A%20%20%20%20%20%20%20%20f%22the%20key%20is%20in%20the%20index%3A%20%7Blist(_grouped.index)%7D%22%2C%0A%20%20%20%20%20%20%20%20f%22after%20reset_index%3A%20%7Blist(_grouped.reset_index().columns)%7D%22%2C%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%22%0A%20%20%20%20%60grouped%5B%22Region%22%5D%60%20on%20that%20frame%20raises%20%60KeyError%3A%20'Region'%60.%20%60.reset_index()%60%20moves%0A%20%20%20%20the%20key%20back%20into%20a%20column%2C%20and%20%60groupby(%22Region%22%2C%20as_index%3DFalse)%60%20never%20moves%20it%20out.%0A%20%20%20%20Both%20are%20steps%20a%20polars%20%60group_by%60%20does%20not%20need%2C%20and%20both%20turn%20up%20in%20almost%20every%20piece%0A%20%20%20%20of%20pandas%20you%20will%20read.%0A%0A%20%20%20%20The%20same%20index%20is%20why%20pandas%20has%20%60.iloc%60%2C%20which%20asks%20for%20a%20row%20by%20position%2C%20and%20%60.loc%60%2C%0A%20%20%20%20which%20asks%20for%20it%20by%20label.%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%20mo.md(r%22%22%22%0A%20%20%20%20%23%23%23%20Methods%20that%20give%20it%20away%0A%0A%20%20%20%20The%20first%20raises.%20%60.iloc%60%20is%20pandas%20only%2C%20so%20the%20moment%20you%20index%20a%20polars%20frame%20by%0A%20%20%20%20position%20you%20find%20out.%0A%0A%20%20%20%20%60%60%60python%0A%20%20%20%20country_summary.iloc%5B0%5D%0A%20%20%20%20%60%60%60%0A%0A%20%20%20%20%60%60%60%0A%20%20%20%20AttributeError%3A%20'DataFrame'%20object%20has%20no%20attribute%20'iloc'%0A%20%20%20%20%60%60%60%0A%0A%20%20%20%20The%20second%20one%20is%20quieter%2C%20because%20it%20exists%20in%20both%20and%20returns%20a%20different%20shape.%0A%20%20%20%20%22%22%22)%0A%20%20%20%20return%0A%0A%0A%40app.cell%0Adef%20_(region_lookup)%3A%0A%20%20%20%20_polars_counts%20%3D%20region_lookup%5B%22Region%22%5D.value_counts(sort%3DTrue)%0A%20%20%20%20_pandas_counts%20%3D%20region_lookup.to_pandas()%5B%22Region%22%5D.value_counts()%0A%0A%20%20%20%20(%0A%20%20%20%20%20%20%20%20f%22polars%20gives%20a%20%7Btype(_polars_counts).__name__%7D%2C%20shape%20%7B_polars_counts.shape%7D%22%2C%0A%20%20%20%20%20%20%20%20_polars_counts%2C%0A%20%20%20%20%20%20%20%20f%22pandas%20gives%20a%20%7Btype(_pandas_counts).__name__%7D%2C%20shape%20%7B_pandas_counts.shape%7D%22%2C%0A%20%20%20%20%20%20%20%20_pandas_counts.to_string()%2C%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%22%0A%20%20%20%20polars%20returns%20a%20**DataFrame**%20with%20two%20columns.%20pandas%20returns%20a%20**Series**%20with%20the%0A%20%20%20%20categories%20in%20the%20index%20and%20the%20counts%20as%20the%20values.%20The%20numbers%20are%20the%20same%20and%20the%0A%20%20%20%20shape%20is%20different%2C%20so%20%60counts%5B%22EU%22%5D%60%20works%20on%20the%20pandas%20one%20and%20fails%20on%20the%20polars%0A%20%20%20%20one%2C%20where%20%60EU%60%20is%20a%20value%20and%20not%20a%20column.%0A%0A%20%20%20%20That%20is%20the%20right%20numbers%20in%20the%20wrong%20shape%2C%20and%20it%20survives%20a%20quick%20look%20at%20the%20output.%0A%0A%20%20%20%20%F0%9F%91%89%20**Try%20it%3A**%20ask%20both%20results%20for%20%60counts%5B%22EU%22%5D%60.%20One%20hands%20back%20a%20number%20and%20the%20other%0A%20%20%20%20raises%20%60ColumnNotFoundError%60.%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%20mo.md(r%22%22%22%0A%20%20%20%20%23%23%23%20Converting%20each%20way%0A%0A%20%20%20%20%60pl.from_pandas(df)%60%20goes%20across%20and%20%60.to_pandas()%60%20comes%20back.%20When%20you%20are%20handed%20a%0A%20%20%20%20pandas%20frame%20in%20a%20course%20whose%20material%20is%20polars%2C%20convert%20once%20at%20the%20top%20and%20stop%0A%20%20%20%20thinking%20about%20it.%0A%0A%20%20%20%20**Reset%20the%20index%20first.**%20%60pl.from_pandas%60%20keeps%20the%20columns%20and%20drops%20the%20index%0A%20%20%20%20without%20a%20word%2C%20and%20yfinance%20keeps%20its%20dates%20in%20the%20index.%20Convert%20its%20prices%20directly%0A%20%20%20%20and%20the%20dates%20are%20gone.%0A%20%20%20%20%22%22%22)%0A%20%20%20%20return%0A%0A%0A%40app.cell%0Adef%20_(pl)%3A%0A%20%20%20%20import%20pandas%20as%20pd%0A%0A%20%20%20%20_prices%20%3D%20pd.DataFrame(%0A%20%20%20%20%20%20%20%20%7B%22Close%22%3A%20%5B227.8%2C%20229.1%2C%20226.4%5D%7D%2C%0A%20%20%20%20%20%20%20%20index%3Dpd.to_datetime(%5B%222026-09-01%22%2C%20%222026-09-02%22%2C%20%222026-09-03%22%5D).rename(%22Date%22)%2C%0A%20%20%20%20)%0A%0A%20%20%20%20(%0A%20%20%20%20%20%20%20%20f%22directly%3A%20%7Bpl.from_pandas(_prices).columns%7D%22%2C%0A%20%20%20%20%20%20%20%20f%22after%20reset_index%3A%20%7Bpl.from_pandas(_prices.reset_index()).columns%7D%22%2C%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%22%0A%20%20%20%20That%20frame%20is%20built%20by%20hand%20in%20the%20shape%20yfinance%20returns%2C%20so%20this%20page%20does%20not%20depend%0A%20%20%20%20on%20the%20network.%20With%20the%20real%20thing%20the%20line%20is%20the%20same%3A%0A%20%20%20%20%60pl.from_pandas(yf.Ticker(%22AAPL%22).history(period%3D%221mo%22).reset_index())%60.%0A%0A%20%20%20%20%F0%9F%91%89%20**Try%20it%3A**%20convert%20%60frame_pd%60%20back%20with%20%60pl.from_pandas(frame_pd)%60%2C%20run%0A%20%20%20%20%60.value_counts(sort%3DTrue)%60%20on%20a%20column%2C%20and%20check%20that%20you%20get%20a%20DataFrame%20back.%0A%0A%20%20%20%20%23%23%23%20%E2%9C%94%20Check%20your%20understanding%0A%0A%20%20%20%201.%20You%20open%20somebody%20else's%20marimo%20notebook%2C%20run%20a%20SQL%20cell%2C%20and%20%60.iloc%5B0%5D%60%20works.%20What%0A%20%20%20%20%20%20%20does%20that%20tell%20you%2C%20and%20where%20is%20the%20setting%20that%20caused%20it%3F%0A%20%20%20%202.%20%60.value_counts()%60%20runs%20on%20both%20libraries%20and%20gives%20the%20same%20numbers.%20What%20breaks%0A%20%20%20%20%20%20%20downstream%2C%20and%20why%20would%20you%20not%20see%20it%20immediately%3F%0A%20%20%20%203.%20You%20convert%20a%20yfinance%20frame%20to%20polars%20and%20the%20date%20column%20is%20missing.%20Where%20did%20it%0A%20%20%20%20%20%20%20go%2C%20and%20what%20one%20call%20would%20have%20kept%20it%3F%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%20mo.md(r%22%22%22%0A%20%20%20%20---%0A%20%20%20%20%23%23%20Chapter%20challenge%0A%0A%20%20%20%20**A%20year%20of%20this%20business.**%20Nothing%20here%20is%20due.%20It%20exists%20because%20each%20section%20above%20teaches%20one%20thing%20and%20one%20real%0A%20%20%20%20question%20needs%20most%20of%20them%20at%20once.%0A%0A%20%20%20%20**Open%20%60data%2Fonline-retail%2Fonline-retail.parquet%60%20and%20answer%20this%3A**%0A%0A%20%20%20%20%3E%20How%20did%20this%20business's%20revenue%20move%20across%20the%20year%2C%20and%20which%20markets%20moved%20it%3F%0A%0A%20%20%20%20**Six%20decisions%20are%20yours%2C%20and%20the%20notebook%20cannot%20make%20them.**%0A%0A%20%20%20%201.%20**What%20counts%20as%20revenue%3F**%20Section%2024%20dropped%20cancellations%2C%20returns%20and%20non-positive%0A%20%20%20%20%20%20%20prices%2C%20and%20still%20left%20postage%2C%20an%20Amazon%20fee%20and%20a%20bad-debt%20adjustment%20in%20the%20total.%0A%20%20%20%20%20%20%20Decide%20what%20stays%2C%20and%20write%20the%20rule%20for%20each%20one.%0A%20%20%20%202.%20**What%20about%20a%20sale%20that%20was%20cancelled%2012%20minutes%20later%3F**%20The%20cancellation%20is%20a%0A%20%20%20%20%20%20%20separate%20row%20with%20a%20%60C%60%20invoice.%20Removing%20the%20cancellation%20alone%20keeps%20the%20sale.%0A%20%20%20%203.%20**The%205%2C268%20exact%20duplicate%20rows.**%20Keeping%20them%20and%20dropping%20them%20are%20both%0A%20%20%20%20%20%20%20defensible.%20State%20which%20you%20did%20and%20what%20it%20changed.%0A%20%20%20%204.%20**December%202011%20is%20nine%20days.**%20Include%20it%2C%20exclude%20it%2C%20or%20annualize%20it%2C%20and%20state%0A%20%20%20%20%20%20%20which.%0A%20%20%20%205.%20**%60CustomerID%60%20is%20missing%20on%20a%20quarter%20of%20the%20rows.**%20That%20decides%20whether%20*revenue%0A%20%20%20%20%20%20%20per%20customer*%20is%20a%20number%20you%20can%20report%20at%20all.%0A%20%20%20%206.%20**Which%20markets%20go%20in%20the%20chart%3F**%20The%20United%20Kingdom%20is%2091.5%20percent%20of%20the%20lines.%0A%0A%20%20%20%20**Done%20looks%20like**%20one%20notebook%20holding%3A%20the%20six%20decisions%20written%20down%20as%20prose%20next%20to%0A%20%20%20%20the%20code%20that%20implements%20them%3B%20a%20monthly%20revenue%20table%3B%20a%20per-market%20summary%3B%20one%20chart%0A%20%20%20%20carrying%20all%20four%20elements%20from%20section%2029%3B%20and%20a%20*How%20I%20know%20these%20numbers%20are%20right*%0A%20%20%20%20cell%20with%20at%20least%20two%20asserts%20that%20pass%20and%20one%20number%20checked%20a%20second%20way.%0A%0A%20%20%20%20**Going%20further.**%20Rebuild%20the%20same%20answer%20with%20%60duckdb.sql(...)%60%20against%20the%20same%0A%20%20%20%20parquet%20file%20and%20reconcile%20the%20two%20totals%20to%20the%20penny%3B%20where%20they%20differ%2C%20find%20out%20which%0A%20%20%20%20one%20is%20wrong%20before%20you%20decide%20which%20one%20to%20keep.%20Then%20add%20the%20country%20to%20region%20lookup%0A%20%20%20%20from%20section%2027%2C%20complete%20it%20for%20all%2038%20countries%20by%20hand%2C%20and%20report%20by%20region.%20Then%0A%20%20%20%20find%20the%20customers%20who%20bought%20in%20the%20first%20quarter%20and%20never%20came%20back%2C%20and%20explain%20what%20your%0A%20%20%20%20definition%20of%20*never%20came%20back*%20does%20to%20the%20number.%20Then%20take%20the%20same%20file%20to%20somebody%0A%20%20%20%20who%20works%20with%20sales%20data%20outside%20this%20course%2C%20show%20them%20your%20revenue%20figure%2C%20and%20write%0A%20%20%20%20down%20the%20first%20thing%20they%20ask%20about%20it.%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%20mo.md(r%22%22%22%0A%20%20%20%20---%0A%20%20%20%20%23%23%20%F0%9F%A4%96%20Ask%20your%20agent%0A%0A%20%20%20%20Two%20different%20uses%2C%20and%20the%20second%20one%20is%20the%20one%20this%20course%20grades.%0A%0A%20%20%20%20%23%23%23%20Ask%20it%20about%20what%20this%20chapter%20left%20out%0A%0A%20%20%20%20Every%20one%20of%20these%20is%20real%20polars%20that%20had%20no%20room%20above.%0A%0A%20%20%20%20-%20This%20chapter%20never%20dropped%20the%205%2C268%20exact%20duplicate%20rows.%20Ask%20about%20%60.is_duplicated()%60%0A%20%20%20%20%20%20and%20%60.unique()%60%2C%20and%20about%20what%20%60subset%3D%60%20and%20%60keep%3D%60%20change.%0A%20%20%20%20-%20%60.fill_null()%60%2C%20%60.drop_nulls()%60%20and%20%60.is_null()%60%20are%20three%20answers%20to%20a%20missing%20value.%0A%20%20%20%20%20%20Ask%20what%20each%20one%20commits%20you%20to%2C%20and%20which%20one%20an%20agent%20reaches%20for%20without%20being%0A%20%20%20%20%20%20asked.%0A%20%20%20%20-%20%60.cast(pl.Float64%2C%20strict%3DFalse)%60%20turns%20a%20value%20that%20will%20not%20convert%20into%20%60null%60%0A%20%20%20%20%20%20instead%20of%20raising.%20Ask%20what%20you%20have%20to%20count%20afterwards.%0A%20%20%20%20-%20%60pl.col(%22Revenue%22).sum().over(%22Country%22)%60%20puts%20a%20group%20total%20back%20on%20every%20row.%20Ask%0A%20%20%20%20%20%20how%20that%20differs%20from%20%60.group_by(%22Country%22).agg(...)%60%20and%20what%20SQL%20calls%20it.%0A%20%20%20%20-%20%60pl.concat%60%20stacks%20frames%20and%20%60.join%60%20widens%20them.%20Ask%20which%20one%20you%20want%20for%0A%20%20%20%20%20%20*12%20monthly%20files*%20and%20which%20for%20*orders%20plus%20customers*.%0A%0A%20%20%20%20Run%20whatever%20comes%20back.%20Paste%20it%20into%20a%20cell%20against%20this%20file%20and%20read%20the%20row%20count.%0A%0A%20%20%20%20%23%23%23%20Watch%20what%20it%20decides%20without%20telling%20you%0A%0A%20%20%20%20Give%20it%20this%2C%20exactly%2C%20with%20the%20file%20attached%20or%20its%20path%20given%3A%0A%0A%20%20%20%20%3E%20*%22Clean%20this%20retail%20transaction%20file%20and%20report%20monthly%20revenue.%22*%0A%0A%20%20%20%20Then%20read%20what%20came%20back%20and%20answer%20these%20questions.%0A%0A%20%20%20%201.%20**Which%20rows%20did%20it%20drop%3F**%20Find%20the%20line.%20Count%20them.%0A%20%20%20%202.%20**What%20did%20it%20do%20with%20the%20invoices%20starting%20%60C%60%3F**%20And%20with%20the%20three%20starting%20%60A%60%3F%0A%20%20%20%203.%20**Did%20it%20drop%20duplicates%3F**%20You%20did%20not%20ask%20it%20to%2C%20and%20you%20did%20not%20ask%20it%20not%20to.%0A%20%20%20%204.%20**How%20did%20it%20get%20the%20month%3F**%20A%20string%20slice%2C%20%60.dt.strftime%60%2C%20or%20something%20else.%0A%20%20%20%205.%20**Does%20its%20final%20month%20cover%20nine%20days%20or%2031%3F**%20Does%20anything%20in%20its%20output%20say%20so%3F%0A%20%20%20%206.%20**Which%20library%20did%20it%20write%2C%20polars%20or%20pandas%3F**%20The%20request%20did%20not%20say.%0A%20%20%20%207.%20**What%20is%20its%20revenue%20total%3F**%20Write%20the%20number%20down.%0A%0A%20%20%20%20Then%20send%20the%20same%20sentence%20again%20in%20a%20fresh%20conversation%20and%20compare%20the%20two%20totals.%0A%0A%20%20%20%20%F0%9F%91%89%20**Try%20it%3A**%20give%20it%20the%20same%20request%20with%20one%20sentence%20added%2C%20*%22and%20tell%20me%20every%20row%0A%20%20%20%20you%20excluded%20and%20why%22*%2C%20and%20see%20how%20much%20of%20the%20above%20you%20no%20longer%20have%20to%20ask.%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%20mo.md(r%22%22%22%0A%20%20%20%20---%0A%20%20%20%20%23%23%20Chapter%20C%20checklist%0A%0A%20%20%20%20-%20%5B%20%5D%20I%20can%20build%20a%20path%20out%20of%20parts%20and%20anchor%20it%20to%20the%20file%20instead%20of%20the%20terminal%0A%20%20%20%20-%20%5B%20%5D%20I%20check%20that%20a%20file%20is%20there%20before%20I%20read%20it%2C%20and%20I%20know%20what%20%60.exists()%60%20returns%0A%20%20%20%20-%20%5B%20%5D%20I%20can%20read%20a%20parquet%20file%2C%20a%20CSV%20and%20a%20JSON%20file%2C%20and%20I%20know%20what%20the%20CSV%20lost%0A%20%20%20%20-%20%5B%20%5D%20I%20can%20read%20the%20error%20polars%20gives%20when%20it%20guesses%20a%20column's%20type%20wrong%2C%20and%20fix%20it%0A%20%20%20%20%20%20%20%20%20%20with%20%60schema_overrides%3D%60%0A%20%20%20%20-%20%5B%20%5D%20I%20know%20which%20of%20%60json.load%60%20and%20%60json.loads%60%20takes%20a%20file%2C%20and%20what%20the%20other%20one%0A%20%20%20%20%20%20%20%20%20%20raises%0A%20%20%20%20-%20%5B%20%5D%20I%20can%20put%20a%20%60try%60%20inside%20a%20loop%20so%20one%20bad%20row%20does%20not%20cost%20me%20the%20other%20400%0A%20%20%20%20-%20%5B%20%5D%20I%20catch%20the%20exception%20I%20expected%20by%20name%2C%20and%20I%20can%20explain%20what%20a%20bare%20%60except%3A%60%20hides%0A%20%20%20%20-%20%5B%20%5D%20I%20can%20write%20an%20assert%20about%20a%20row%20count%2C%20a%20total%2C%20and%20a%20join%2C%20and%20I%20know%20%60assert%60%0A%20%20%20%20%20%20%20%20%20%20is%20a%20development%20check%0A%20%20%20%20-%20%5B%20%5D%20I%20can%20ask%20a%20frame%20its%20shape%2C%20its%20columns%2C%20its%20types%20and%20its%20first%20rows%0A%20%20%20%20-%20%5B%20%5D%20I%20can%20tell%20a%20Series%20from%20a%20DataFrame%20and%20state%20what%20%60.select()%60%20gives%20back%0A%20%20%20%20-%20%5B%20%5D%20I%20know%20that%20a%20money%20column%20can%20arrive%20as%20%60Decimal%60%2C%20and%20that%20multiplying%20it%20by%20a%0A%20%20%20%20%20%20%20%20%20%20float%20quietly%20makes%20it%20a%20float%0A%20%20%20%20-%20%5B%20%5D%20I%20can%20filter%20with%20%60pl.col%60%2C%20combine%20two%20conditions%20with%20%60%26%60%2C%20and%20remember%20the%0A%20%20%20%20%20%20%20%20%20%20parentheses%0A%20%20%20%20-%20%5B%20%5D%20I%20can%20write%20a%20cleaning%20rule%20as%20a%20line%20of%20code%20with%20the%20decision%20in%20a%20comment%0A%20%20%20%20%20%20%20%20%20%20beside%20it%0A%20%20%20%20-%20%5B%20%5D%20I%20sort%20by%20the%20column%20a%20cleaning%20rule%20was%20meant%20to%20fix%2C%20and%20read%20the%20top%20rows%0A%20%20%20%20-%20%5B%20%5D%20I%20can%20explain%20why%20%60order_date%5B%3A7%5D%60%20is%20a%20month%20in%20one%20file%20and%20garbage%20in%20another%0A%20%20%20%20-%20%5B%20%5D%20I%20can%20parse%20a%20date%20column%20with%20a%20format%2C%20and%20I%20know%20what%20%60strict%3DFalse%60%20changes%0A%20%20%20%20-%20%5B%20%5D%20I%20can%20pull%20the%20month%2C%20the%20weekday%20and%20the%20hour%20out%20of%20a%20date%20column%20with%20%60.dt%60%0A%20%20%20%20-%20%5B%20%5D%20I%20ask%20for%20a%20day%20with%20a%20half-open%20window%20instead%20of%20an%20equality%0A%20%20%20%20-%20%5B%20%5D%20I%20check%20whether%20the%20last%20period%20in%20a%20time%20series%20is%20complete%20before%20I%20chart%20it%0A%20%20%20%20-%20%5B%20%5D%20I%20can%20replace%20a%20counting%20loop%20with%20%60.value_counts(sort%3DTrue)%60%2C%20and%20I%20read%20the%0A%20%20%20%20%20%20%20%20%20%20%60null%60%20row%0A%20%20%20%20-%20%5B%20%5D%20I%20answer%20three%20questions%20before%20writing%20a%20%60group_by%60%2C%20and%20I%20name%20the%20output%20columns%0A%20%20%20%20-%20%5B%20%5D%20I%20state%20what%20shape%20the%20result%20should%20be%2C%20then%20assert%20it%0A%20%20%20%20-%20%5B%20%5D%20I%20end%20every%20%60group_by%60%20with%20a%20%60.sort()%60%0A%20%20%20%20-%20%5B%20%5D%20I%20can%20explain%20what%20each%20of%20the%20%60how%3D%60%20values%20keeps%0A%20%20%20%20-%20%5B%20%5D%20I%20state%20a%20join's%20cardinality%20with%20%60validate%3D%60%2C%20and%20I%20know%20when%20the%20row%20count%20is%0A%20%20%20%20%20%20%20%20%20%20supposed%20to%20change%0A%20%20%20%20-%20%5B%20%5D%20I%20run%20an%20anti%20join%20to%20see%20what%20did%20not%20match%2C%20instead%20of%20assuming%20everything%20did%0A%20%20%20%20-%20%5B%20%5D%20I%20name%20my%20own%20join%20suffix%0A%20%20%20%20-%20%5B%20%5D%20I%20can%20pivot%20a%20long%20table%20to%20wide%20and%20unpivot%20a%20wide%20one%20back%0A%20%20%20%20-%20%5B%20%5D%20I%20compute%20in%20long%20and%20present%20in%20wide%0A%20%20%20%20-%20%5B%20%5D%20I%20can%20draw%20a%20chart%20carrying%20a%20finding%2C%20named%20axes%2C%20readable%20scales%20and%20a%20legend%0A%20%20%20%20-%20%5B%20%5D%20I%20aggregate%20before%20I%20plot%0A%20%20%20%20-%20%5B%20%5D%20I%20can%20recognize%20a%20matplotlib%20chart%20and%20find%20its%20four%20%60set_%60%20calls%0A%20%20%20%20-%20%5B%20%5D%20I%20run%20%60type(df)%60%20on%20a%20frame%20I%20did%20not%20create%2C%20and%20I%20know%20what%20%60.iloc%60%20working%20means%0A%0A%20%20%20%20**Chapter%20D%20is%20Agents%20and%20APIs**%3A%20what%20a%20request%20to%20a%20model%20actually%20contains%2C%20why%20it%0A%20%20%20%20remembers%20nothing%2C%20and%20what%20it%20costs.%0A%20%20%20%20%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
163b65bf5c1893a1f37fce406a8f028f