Compare commits
10 commits
cceb1d1ec0
...
04bbfb09a5
Author | SHA1 | Date | |
---|---|---|---|
04bbfb09a5 | |||
4ec27ed73b | |||
4306a7d246 | |||
e90b423ebc | |||
8cbe6c3571 | |||
d067d41267 | |||
fc22e0cc02 | |||
824ad25e60 | |||
7d1d929b0e | |||
fed35fcfd2 |
10 changed files with 650 additions and 148 deletions
5
.gitignore
vendored
Normal file
5
.gitignore
vendored
Normal file
|
@ -0,0 +1,5 @@
|
|||
/.quarto/
|
||||
/output/
|
||||
/*_files/
|
||||
|
||||
*.ipynb
|
19
_quarto-blog.yml
Normal file
19
_quarto-blog.yml
Normal file
|
@ -0,0 +1,19 @@
|
|||
project:
|
||||
type: default
|
||||
output-dir: /home/marty/projects/hosting/webpage/src/content/blog/2024-07-02-nuclear-explosions-analysis
|
||||
render:
|
||||
- index.qmd
|
||||
post-render:
|
||||
- tools/fix-astro-img.py
|
||||
|
||||
format:
|
||||
hugo-md:
|
||||
preserve-yaml: true
|
||||
code-fold: true
|
||||
typst:
|
||||
toc: true
|
||||
echo: false
|
||||
citeproc: true
|
||||
docx:
|
||||
toc: true
|
||||
echo: false
|
35
_quarto-default.yml
Normal file
35
_quarto-default.yml
Normal file
|
@ -0,0 +1,35 @@
|
|||
project:
|
||||
type: default
|
||||
output-dir: output
|
||||
render:
|
||||
- index.qmd
|
||||
- meta.md
|
||||
|
||||
execute:
|
||||
cache: true
|
||||
|
||||
format:
|
||||
html:
|
||||
code-fold: true
|
||||
toc: true
|
||||
echo: true
|
||||
typst:
|
||||
toc: true
|
||||
echo: false
|
||||
citeproc: true
|
||||
docx:
|
||||
toc: true
|
||||
echo: false
|
||||
# pdf: # BREAKS ON 'GREAT TABLES' python lib tables
|
||||
# echo: false # since we want to see the code in this case
|
||||
# papersize: A4
|
||||
# # geometry:
|
||||
# # - left=2cm
|
||||
# # - right=2.5cm
|
||||
# # - top=2.5cm
|
||||
# # - bottom=2.5cm
|
||||
# indent: true
|
||||
# linestretch: 1.5
|
||||
# fontfamily: lmodern
|
||||
# fontsize: "12"
|
||||
# pdf-engine: tectonic
|
6
_quarto.yml
Normal file
6
_quarto.yml
Normal file
|
@ -0,0 +1,6 @@
|
|||
author: Marty Oehme
|
||||
csl: https://www.zotero.org/styles/apa
|
||||
|
||||
profile:
|
||||
group:
|
||||
- [default, blog]
|
40
data/metasheet.md
Normal file
40
data/metasheet.md
Normal file
|
@ -0,0 +1,40 @@
|
|||
# Nuclear Explosions
|
||||
|
||||
This week's [**data**](nuclear_explosions.csv) is from [Stockholm International Peace Research Institute](https://github.com/data-is-plural/nuclear-explosions/blob/master/documents/sipri-report-original.pdf), by way of [data is plural](https://github.com/data-is-plural/nuclear-explosions) with credit to [Jesus Castagnetto](https://github.com/rfordatascience/tidytuesday/issues/91) for sharing the dataset.
|
||||
|
||||
Additional information can be found on [Wikipedia](https://en.wikipedia.org/wiki/List_of_nuclear_weapons_tests) or via the original report [PDF](https://github.com/data-is-plural/nuclear-explosions/blob/master/documents/sipri-report-original.pdf).
|
||||
|
||||
Additional related datasets can be found at [Our World in Data](https://ourworldindata.org/nuclear-weapons).
|
||||
|
||||
For details around units for yield/magnitude, please see the [Nuclear Yield](https://seismo.berkeley.edu/~rallen/research/nuke/yield.html) formulas.
|
||||
|
||||
|
||||
# Get the data!
|
||||
|
||||
```
|
||||
nuclear_explosions <- readr::read_csv("https://raw.githubusercontent.com/rfordatascience/tidytuesday/master/data/2019/2019-08-20/nuclear_explosions.csv")
|
||||
|
||||
```
|
||||
|
||||
# Data Dictionary
|
||||
|
||||
## `nuclear_explosions.csv`
|
||||
|
||||
|variable |class |description |
|
||||
|:--- |:--- |:-----------|
|
||||
|date_long |date | ymd date|
|
||||
|year |double | year of explosion |
|
||||
|id_no |double | unique ID |
|
||||
|country |character | Country deploying the nuclear device |
|
||||
|region |character | Region where nuclear device was deployed |
|
||||
|source |character | Source the reported the explosion event |
|
||||
|latitude |double | Latitude position |
|
||||
|longitude |double | Longitude position |
|
||||
|magnitude_body |double | Body wave magnitude of explosion (mb)|
|
||||
|magnitude_surface |double | Surface wave magnitude of explosion (Ms) |
|
||||
|depth |double | Depth at detonation in Km (could be underground or above ground) -- please note that positive = depth (below ground), while negative = height (above ground) |
|
||||
|yield_lower |double | Explosion yield lower estimate in kilotons of TNT |
|
||||
|yield_upper |double | Explosion yield upper estimate in kilotons of TNT |
|
||||
|purpose |character | Purpose of detonation: COMBAT (WWII bombs dropped over Japan), FMS (Soviet test, study phenomenon of nuclear explosion), ME (Military Exercise), PNE (Peaceful nuclear explosion), SAM (Soviet test, accidental mode/emergency), SSE (French/US tests - testing safety of nuclear weapons in case of accident), TRANSP (Transportation-storage purposes), WE (British, French, US, evaluate effects of nuclear detonation on various targets), WR (Weapons development program) |
|
||||
|name |character | Name of event or bomb |
|
||||
|type |character | type - method of deployment -- ATMOSPH (Atmospheric), UG (underground), BALLOON (Balloon drop), AIRDROP (Airplane deployed), ROCKET (Rocket deployed), TOWER (deplyed at top of constructed tower), WATERSURFACE (on surface of body of water), BARGE (on barge boat), SURFACE (on surface or in shallow crater), UW (Underwater), SHAFT (Vertical Shaft underground), TUNNEL/GALLERY (Horizontal tunnel) |
|
|
@ -1,7 +1,7 @@
|
|||
---
|
||||
title: Nuclear Explosions
|
||||
author: Marty Oehme
|
||||
output-dir: out
|
||||
subtitle: "Using python polars and seaborn to visualize global detonations"
|
||||
description: "Using python polars and seaborn to visualize global detonations"
|
||||
references:
|
||||
- type: techreport
|
||||
id: Bergkvist2000
|
||||
|
@ -18,16 +18,10 @@ references:
|
|||
title: "Nuclear Explosions 1945 - 1998"
|
||||
page: 1-42
|
||||
issn: 1104-9154
|
||||
format:
|
||||
html:
|
||||
toc: true
|
||||
code-fold: true
|
||||
typst:
|
||||
toc: true
|
||||
echo: false
|
||||
docx:
|
||||
toc: true
|
||||
echo: false
|
||||
pubDate: "2024-07-03T18:36:26"
|
||||
weight: 10
|
||||
tags:
|
||||
- python
|
||||
---
|
||||
|
||||
```{python}
|
||||
|
@ -43,7 +37,21 @@ from matplotlib import pyplot as plt
|
|||
|
||||
sns.set_theme(style="darkgrid")
|
||||
sns.set_context("notebook")
|
||||
cp=sns.color_palette()
|
||||
country_colors = {
|
||||
"US": cp[0],
|
||||
"USSR": cp[3],
|
||||
"France": cp[6],
|
||||
"UK": cp[5],
|
||||
"China": cp[4],
|
||||
"India": cp[1],
|
||||
"Pakistan": cp[2],
|
||||
}
|
||||
```
|
||||
|
||||
```{python}
|
||||
# | label: data-prep
|
||||
# | code-fold: true
|
||||
schema_overrides = (
|
||||
{
|
||||
col: pl.Categorical
|
||||
|
@ -53,13 +61,31 @@ schema_overrides = (
|
|||
| {col: pl.String for col in ["year", "name"]}
|
||||
)
|
||||
|
||||
cty_alias = {
|
||||
"PAKIST": "Pakistan",
|
||||
"FRANCE": "France",
|
||||
"CHINA": "China",
|
||||
"INDIA": "India",
|
||||
"USA": "US",
|
||||
}
|
||||
|
||||
|
||||
def cty_replace(name: str) -> str:
|
||||
if name in cty_alias:
|
||||
return cty_alias[name]
|
||||
return name
|
||||
|
||||
|
||||
df = (
|
||||
pl.read_csv(
|
||||
"data/nuclear_explosions.csv",
|
||||
schema_overrides=schema_overrides,
|
||||
null_values=["NA"],
|
||||
)
|
||||
.with_columns(date=pl.col("year").str.strptime(pl.Date, "%Y"))
|
||||
.with_columns(
|
||||
date=pl.col("year").str.strptime(pl.Date, "%Y"),
|
||||
country=pl.col("country").map_elements(cty_replace, return_dtype=pl.String),
|
||||
)
|
||||
.with_columns(year=pl.col("date").dt.year().cast(pl.Int32))
|
||||
)
|
||||
```
|
||||
|
@ -69,7 +95,7 @@ df = (
|
|||
The following is a re-creation and expansion of some of the graphs found in the
|
||||
@Bergkvist2000 produced report on nuclear explosions between 1945 and 1998. It
|
||||
is primarily a reproduction of key plots from the original report.
|
||||
Additionally, it serves as a exercise in plotting with the python library
|
||||
Additionally, it serves as an exercise in plotting with the python library
|
||||
seaborn and the underlying matplotlib. Lastly, it approaches some less well
|
||||
tread territory for data science in the python universe as it uses the python
|
||||
library polars-rs for data loading and transformation. All the code used to
|
||||
|
@ -77,9 +103,9 @@ transform the data and create the plots is available directly within the full
|
|||
text document, and separately as well. PDF and Docx formats are available with
|
||||
the plotting results only.
|
||||
|
||||
Their original purpose was the collection of a long list of all the nuclear
|
||||
explosions occurring between those years, as well as analysing the responsible
|
||||
nations, tracking the types and purposes of the explosions, as well as
|
||||
The authors' original purpose was the collection of a long list of all the
|
||||
nuclear explosions occurring between those years, as well as analysing the
|
||||
responsible nations, tracking the types and purposes of the explosions and
|
||||
connecting the rise and fall of nuclear explosion numbers to historical events
|
||||
throughout.
|
||||
|
||||
|
@ -90,13 +116,13 @@ throughout.
|
|||
## Nuclear devices
|
||||
|
||||
There are two main kinds of nuclear device: those based entirely, on fission,
|
||||
or the splitting of heavy atomic nucleii (previously known as atomic devices)
|
||||
or the splitting of heavy atomic nuclei (previously known as atomic devices)
|
||||
and those in which the main energy is obtained by means of fusion, or of -light
|
||||
atomic nucleii (hydrogen or thermonuclear devices). A fusion explosion must
|
||||
atomic nuclei (hydrogen or thermonuclear devices). A fusion explosion must
|
||||
however be initiated with the help of a fission device. The strength of a
|
||||
fusion explosion can be practically unlimited. The explosive power of a
|
||||
nuclear explosion is expressed in ktlotons, (kt) or megatons (Mt), which
|
||||
correspond to 1000 and i million'tonnes, of conventional explosive (TNT),
|
||||
nuclear explosion is expressed in kilotons, (kt) or megatons (Mt), which
|
||||
correspond to 1000 and 1 million tonnes, of conventional explosive (TNT),
|
||||
respectively.
|
||||
|
||||
[@Bergkvist2000, 6]
|
||||
|
@ -108,8 +134,9 @@ each country had explode, seen in @tbl-yields.
|
|||
```{python}
|
||||
# | label: tbl-yields
|
||||
# | tbl-cap: "Total number and yields of explosions"
|
||||
# | output: asis
|
||||
|
||||
from great_tables import GT, md
|
||||
from great_tables import GT
|
||||
|
||||
df_yields = (
|
||||
df.select(["country", "id_no", "yield_lower", "yield_upper"])
|
||||
|
@ -119,11 +146,17 @@ df_yields = (
|
|||
pl.col("id_no").len().alias("count"),
|
||||
pl.col("yield_avg").sum(),
|
||||
)
|
||||
# .with_columns(country=pl.col("country").cast(pl.String).str.to_titlecase())
|
||||
.with_columns(yield_per_ex=pl.col("yield_avg") / pl.col("count"))
|
||||
.sort("count", descending=True)
|
||||
)
|
||||
|
||||
(
|
||||
us_row = df_yields.filter(pl.col("country") == "US")
|
||||
yields_above_us = df_yields.filter(
|
||||
pl.col("yield_per_ex") > us_row["yield_per_ex"]
|
||||
).sort("yield_per_ex", descending=True)
|
||||
assert len(yields_above_us) == 3, "Yield per explosion desc needs updating!"
|
||||
|
||||
tab=(
|
||||
GT(df_yields)
|
||||
.tab_source_note(
|
||||
source_note="Source: Author's elaboration based on Bergkvist and Ferm (2000)."
|
||||
|
@ -131,22 +164,33 @@ df_yields = (
|
|||
.tab_spanner(label="Totals", columns=["count", "yield_avg"])
|
||||
.tab_stub(rowname_col="country")
|
||||
.tab_stubhead(label="Country")
|
||||
.cols_label(
|
||||
count="Count",
|
||||
yield_avg="Yield in kt",
|
||||
)
|
||||
.cols_label(count="Count", yield_avg="Yield in kt", yield_per_ex="Yield average")
|
||||
.fmt_integer(columns="count")
|
||||
.fmt_number(columns="yield_avg", decimals=1)
|
||||
.fmt_number(columns="yield_per_ex", decimals=1)
|
||||
)
|
||||
del df_yields
|
||||
tab
|
||||
```
|
||||
|
||||
It is interesting to note that while the US undoubtedly had the highest raw
|
||||
number of explosions, it did not, in fact, output the highest estimated
|
||||
detonation yields.
|
||||
In fact, `{python} len(yields_above_us)` countries have a higher average
|
||||
explosion yield per detonation than the US:
|
||||
`{python} yields_above_us[0]["country"].item()` leads with an average of
|
||||
`{python} f"{yields_above_us[0]['yield_per_ex'].item():.2f}"` kt,
|
||||
before
|
||||
`{python} yields_above_us[1]["country"].item()` with an average of
|
||||
`{python} f"{yields_above_us[1]['yield_per_ex'].item():.2f}"` kt.
|
||||
|
||||
## Numbers over time
|
||||
|
||||
When investigating the nuclear explosions in the world, let us first start by
|
||||
looking at how many explosions occurred each year in total. This hides the
|
||||
specific details of who was responsible and which types were involved but
|
||||
instead paints a much stronger picture of the overall dimension of nuclear
|
||||
testing, as can be seen in @fig-total.
|
||||
In the examination of global nuclear detonations, our initial focus shall be
|
||||
quantifying the annual incidence of the events in aggregate. While it obscures
|
||||
the specific details of the responsible nations and which diversity of types
|
||||
tested, it instead paints a much stronger picture of the overall abstracted
|
||||
dimension of nuclear testing throughout history, as depicted in @fig-total.
|
||||
|
||||
```{python}
|
||||
# | label: fig-total
|
||||
|
@ -171,17 +215,29 @@ with sns.axes_style(
|
|||
del per_year
|
||||
```
|
||||
|
||||
As we can see, the numbers of explosions rise increasingly towards 1957 and
|
||||
sharply until 1958, before dropping off for a year in 1959. The reasons for
|
||||
this drop are not entirely clear, but it is very likely that the data are
|
||||
simply missing for these years.
|
||||
<!-- FIXME: The reasons for this are a non-proliferation pact, in article -->
|
||||
As we can see, the number of explosions rises increasingly towards 1957 and
|
||||
sharply until 1958, before dropping off for a year in 1959. The reason for this
|
||||
drop should primarily be found in the start of the 'Treaty of Test Ban' which
|
||||
put limits and restraints on the testing of above-ground nuclear armaments, as
|
||||
discussed in the original article. Above all the contract signals the
|
||||
prohibition of radioactive debris to fall beyond a nation's respective
|
||||
territorial bounds.
|
||||
|
||||
However, this contract should perhaps not be viewed as the only reason: With
|
||||
political and cultural shifts throughout the late 1950s and early 1960s
|
||||
increasingly focusing on the fallout and horror of nuclear warfare a burgeoning
|
||||
public opposition to nuclear testing and instead a push towards disarmament was
|
||||
taking hold. The increased focus on the space race between the US and USSR may
|
||||
have detracted from the available funds, human resources and agenda attention
|
||||
for nuclear testing. Lastly, with nuclear testing policies strongly shaped by
|
||||
the political dynamics of the Cold War, a period of improved diplomatic
|
||||
relations such as the late 1950s prior to the Cuban missile crisis may directly
|
||||
affect the output of nuclear testing facilities between various powers.
|
||||
|
||||
<!-- TODO: Extract exact numbers from data on-the-fly -->
|
||||
There is another, very steep, rise in 1962 with over 175 recorded explosions,
|
||||
before an even sharper drop-off the following year down to just 50 explosions.
|
||||
|
||||
Afterwards the changes appear less sharp and the changes remain between 77 and
|
||||
Afterward the changes appear less sharp and the changes remain between 77 and
|
||||
24 explosions per year, with a slight downward tendency.
|
||||
|
||||
While these numbers show the overall proliferation of nuclear power, let us now
|
||||
|
@ -191,6 +247,7 @@ of explosions over time by country can be seen in @fig-percountry.
|
|||
```{python}
|
||||
# | label: fig-percountry
|
||||
# | fig-cap: "Nuclear explosions by country, 1945-98"
|
||||
|
||||
keys = df.select("date").unique().join(df.select("country").unique(), how="cross")
|
||||
per_country = keys.join(
|
||||
df.group_by(["date", "country"], maintain_order=True).len(),
|
||||
|
@ -199,7 +256,7 @@ per_country = keys.join(
|
|||
coalesce=True,
|
||||
).with_columns(pl.col("len").fill_null(0))
|
||||
|
||||
g = sns.lineplot(data=per_country, x="date", y="len", hue="country")
|
||||
g = sns.lineplot(data=per_country, x="date", y="len", hue="country", palette=country_colors)
|
||||
g.set_xlabel("Year")
|
||||
g.set_ylabel("Count")
|
||||
plt.setp(
|
||||
|
@ -211,14 +268,14 @@ del per_country
|
|||
|
||||
Once again we can see the visibly steep ramp-up to 1962, though it becomes
|
||||
clear that this was driven both by the USSR and the US. Of course the graph
|
||||
also makes visible the sheer unmatched number of explosions emenating from both
|
||||
also makes visible the sheer unmatched number of explosions emanating from both
|
||||
of the countries, with only France catching up to the US numbers and China
|
||||
ultimately overtaking them in the 1990s.
|
||||
|
||||
However, here it also becomes more clear how the UK was responsible for some
|
||||
early explosions in the late 1950s and early 1960s already, as well as the rise
|
||||
in France's nuclear testing from the early 1960s onwards to around 1980, before
|
||||
slowly decreasing in intensity afterwards.
|
||||
slowly decreasing in intensity afterward.
|
||||
|
||||
Let us turn to a cross-cut through the explosions in @fig-groundlevel, focusing
|
||||
on the number of explosions that have occurred underground and above-ground
|
||||
|
@ -273,6 +330,7 @@ with sns.axes_style("darkgrid", {"xtick.bottom": True, "ytick.left": True}):
|
|||
hue="country",
|
||||
multiple="stack",
|
||||
binwidth=365,
|
||||
palette=country_colors,
|
||||
)
|
||||
|
||||
g.xaxis.set_major_locator(mdates.YearLocator(base=5))
|
||||
|
@ -293,25 +351,23 @@ shift from above-ground to underground tests, starting with the year 1962.
|
|||
|
||||
## Locations
|
||||
|
||||
Finally, let's view a map of the world with the explosions marked.
|
||||
Finally, let's view a map of the world with the explosions marked, separated by country.
|
||||
::: {.content-visible when-format="html"}
|
||||
Hovering over individual explosions will show their year
|
||||
while a click will open more information in a panel.
|
||||
The map can be seen in @fig-worldmap-html.
|
||||
:::
|
||||
|
||||
::: {.content-visible unless-format="html"}
|
||||
The map can be seen in @fig-worldmap-static.
|
||||
:::
|
||||
|
||||
```{python}
|
||||
# | label: fig-worldmap
|
||||
# | fig-cap: "World map of nuclear explosions, 1945-98"
|
||||
# | label: worldmap-setup
|
||||
# | output: false
|
||||
import folium
|
||||
import geopandas as gpd
|
||||
from shapely.geometry import Point
|
||||
|
||||
def set_style() -> pl.Expr:
|
||||
return (
|
||||
pl.when(pl.col("country") == "USSR")
|
||||
.then(pl.lit({"color": "red"}, allow_object=True))
|
||||
.otherwise(pl.lit({"color": "blue"}, allow_object=True))
|
||||
)
|
||||
|
||||
|
||||
geom = [Point(xy) for xy in zip(df["longitude"], df["latitude"])]
|
||||
# df_pd = df.with_columns(style=set_style()).to_pandas().set_index("date")
|
||||
df_pd = df.with_columns().to_pandas().set_index("date")
|
||||
gdf = gpd.GeoDataFrame(
|
||||
df_pd,
|
||||
|
@ -320,25 +376,18 @@ gdf = gpd.GeoDataFrame(
|
|||
)
|
||||
del df_pd
|
||||
|
||||
country_colors = {
|
||||
"USA": "darkblue",
|
||||
"USSR": "darkred",
|
||||
"FRANCE": "pink",
|
||||
"UK": "black",
|
||||
"CHINA": "purple",
|
||||
"INDIA": "orange",
|
||||
"PAKIST": "green",
|
||||
}
|
||||
def rgb_to_hex(rgb: tuple[float,float,float]) -> str:
|
||||
return "#" + "".join([format(int(c*255), '02x') for c in rgb])
|
||||
|
||||
m = folium.Map(tiles="cartodb positron")
|
||||
for country in country_colors.keys():
|
||||
fg = folium.FeatureGroup(name=country, show=True).add_to(m)
|
||||
folium.GeoJson(
|
||||
gdf[gdf["country"].str.contains(country)],
|
||||
gdf[gdf["country"] == country],
|
||||
name="Nuclear Explosions",
|
||||
marker=folium.Circle(radius=3, fill_opacity=0.4),
|
||||
style_function=lambda x: {
|
||||
"color": country_colors[x["properties"]["country"]],
|
||||
"color": rgb_to_hex(country_colors[x["properties"]["country"]]),
|
||||
"radius": (
|
||||
x["properties"]["magnitude_body"]
|
||||
if x["properties"]["magnitude_body"] > 0
|
||||
|
@ -368,15 +417,60 @@ for country in country_colors.keys():
|
|||
),
|
||||
).add_to(fg)
|
||||
folium.LayerControl().add_to(m)
|
||||
```
|
||||
|
||||
::: {.content-visible when-format="html"}
|
||||
|
||||
```{python}
|
||||
# | label: fig-worldmap-html
|
||||
# | fig-cap: World map of nuclear explosions, 1945-98
|
||||
m
|
||||
```
|
||||
|
||||
That is all for now.
|
||||
There are undoubtedly more explorations to undertake,
|
||||
but this is it for the time being.
|
||||
:::
|
||||
|
||||
<!-- Ideas TODO:
|
||||
- do not just use 'count' of explosions but yields
|
||||
- compare number to yields for ctrys
|
||||
- count up total number per country in table
|
||||
-->
|
||||
::: {.content-visible unless-format="html" width=80%}
|
||||
|
||||
```{python}
|
||||
# | label: fig-worldmap-static
|
||||
# | fig-cap: World map of nuclear explosions, 1945-98
|
||||
# ENSURE SELENIUM IS INSTALLED
|
||||
from PIL import Image
|
||||
from IPython.display import Image as IImage
|
||||
import io
|
||||
img = m._to_png()
|
||||
|
||||
bimg = io.BytesIO(img)
|
||||
Image.open(bimg).save("map.png")
|
||||
IImage(url="map.png")
|
||||
```
|
||||
|
||||
:::
|
||||
|
||||
::: {.callout-warning .content-visible when-format="markdown"}
|
||||
Interactive maps not working
|
||||
|
||||
Unfortunately, as of right now folium maps rendered within a quarto document do
|
||||
not seem to translate terribly well into an astro blog such as this.
|
||||
This is why, for now, there is only a static image here.
|
||||
|
||||
This is very sad, but for the time being feel free to download and peruse
|
||||
the ipynb notebook [here](./index.ipynb), or the [pdf](./index.pdf)
|
||||
or [docx](./index.docx) versions.
|
||||
:::
|
||||
|
||||
While there are undoubtedly more aspects of the data that provide interesting
|
||||
patterns for analysis, this shall be the extent of review for the time being
|
||||
for this reproduction.
|
||||
|
||||
We can see how the combination of python polars and seaborn makes the process
|
||||
relatively approachable, understandable and, combined with the rendering output
|
||||
by quarto, fully reproducible.
|
||||
|
||||
Additionally, we can see how additional projects can be included to produce
|
||||
interactive graphs and maps with tools such as folium and geopandas.
|
||||
|
||||
## References
|
||||
|
||||
::: {#refs}
|
||||
:::
|
157
meta.md
Normal file
157
meta.md
Normal file
|
@ -0,0 +1,157 @@
|
|||
This page documents some meta observations about my time recreating the nuclear explosions in this post,
|
||||
mostly some little tips to work well with python polars and seaborn, or little tricks to integrate them and geopandas visualizations.
|
||||
|
||||
## From a lat/long polars dataframe to geopandas
|
||||
|
||||
To go from a polars frame to one we can use for GIS operations with geopandas is fairly simple:
|
||||
We first move from a polars to an indexed pandas frame, in this case I have indexed on the date of each explosion.
|
||||
|
||||
We can use this intermediate dataframe to fill a geopandas frame which is built from the points of lat/long columns,
|
||||
using the `gpd.points_from_xy()` function to create spatial `Point` objects from simple pandas Series.
|
||||
Finally, we need to set a 'crs=' mapping for which this visualization simply uses the `EPSG:4326` global offsets (will generally be the same for global mappings).
|
||||
|
||||
```python
|
||||
df_pd = df.with_columns().to_pandas().set_index("date")
|
||||
gdf = gpd.GeoDataFrame(
|
||||
df_pd,
|
||||
crs="EPSG:4326",
|
||||
geometry=gpd.points_from_xy(x=df_pd["longitude"], y=df_pd["latitude"]),
|
||||
)
|
||||
del df_pd
|
||||
```
|
||||
|
||||
## Keeping the same seaborn color palette for the same categories
|
||||
|
||||
For the analysis, I have multiple plots which distinguish between the different countries undertaking nuclear detonations.
|
||||
The country category thus appears repeatedly, and with static values (i.e. it will always contain 'US', 'USSR', 'China', 'France' and so on).
|
||||
|
||||
Now, seaborn has very nice functionality to automatically give different hues to categories like these in plots,
|
||||
but how do we ensure that the hues given remain _the same_ throughout?
|
||||
|
||||
One way of achieving it would be to keep the order of categories the same throughout all plots.
|
||||
However, this seems hidden,
|
||||
often adds to the strain of just getting to the right data frame calculations,
|
||||
appears a little too magic for my liking and, to top it off,
|
||||
is even harder to achieve with some of polars' parallelized operations.
|
||||
|
||||
Instead we can explicitly map our categories to colors.
|
||||
In my case, my categories for this example will always be the different countries:
|
||||
|
||||
```python
|
||||
country_colors = {
|
||||
"US": 'blue',
|
||||
"USSR": 'red',
|
||||
"France": 'pink'
|
||||
"UK": 'black'
|
||||
"China": 'purple'
|
||||
"India": 'orange'
|
||||
"Pakistan": 'green'
|
||||
}
|
||||
```
|
||||
|
||||
These are colors seaborn understands and can be given to a plot via the keyword option `palette=country_colors` which will pass along the colors above to the respective plot.
|
||||
|
||||
However, one advantage of seaborn is its nice in-built color schemes (i.e. palettes) which we will not make use of if we instead hard-code our color preferences like this.
|
||||
Instead, we can directly access seaborn's color palette with `sns.color_palette()` which we can then use to explicitly map our categories to colors:
|
||||
|
||||
```python
|
||||
cp=sns.color_palette()
|
||||
country_colors = {
|
||||
"US": cp[0],
|
||||
"USSR": cp[3],
|
||||
"France": cp[6],
|
||||
"UK": cp[5],
|
||||
"China": cp[4],
|
||||
"India": cp[1],
|
||||
"Pakistan": cp[2],
|
||||
}
|
||||
```
|
||||
|
||||
This mapping is passed exactly the same way as the other.
|
||||
Now, we've ensured that colors in plots (that have the countries as hue category) will all have the same color for the same country throughout.
|
||||
At the same time we have a single spot in which we can change the actual color theme seaborn uses, instead of hard-coding our preferences throughout.
|
||||
|
||||
This I find very useful when creating analyses with similar categories throughout,
|
||||
|
||||
In the nuclear analysis there is a folium geospatial (GeoJson) map at the very end which uses colors to distinguish between the countries once again.
|
||||
Here we can make use of almost the same strategy, with the one caveat that folium expects the colors in hexadecimal format, while seaborn internally stores them as RGB value tuples.
|
||||
|
||||
What we can do, then is to use a simple translation function which converts from one format to the other on the fly,
|
||||
and inject that into the map creation method of folium:
|
||||
|
||||
```python
|
||||
def rgb_to_hex(rgb: tuple[float,float,float]) -> str:
|
||||
return "#" + "".join([format(int(c*255), '02x') for c in rgb])
|
||||
|
||||
|
||||
map = folium.Map(tiles="cartodb positron")
|
||||
folium.GeoJson(
|
||||
gdf,
|
||||
name="Nuclear Explosions",
|
||||
marker=folium.Circle(radius=3, fill_opacity=0.4),
|
||||
style_function=lambda x: {
|
||||
"color": rgb_to_hex(country_colors[x["properties"]["country"]]),
|
||||
"radius": (
|
||||
x["properties"]["magnitude_body"]
|
||||
if x["properties"]["magnitude_body"] > 0
|
||||
else 1.0
|
||||
)
|
||||
* 10,
|
||||
},
|
||||
).add_to(map)
|
||||
```
|
||||
|
||||
## Using dictionary keys to create folium map layers
|
||||
|
||||
As a bonus we can even use our color category keys to create different layers on the folium map which can be turned on and off individually.
|
||||
Thus we can decide which country's detonations we want to visualize.
|
||||
|
||||
Of course, we could also create these keys dynamically from the polars dataframe by extracting the `.unique()` elements of its "country" column (even though we use pandas geoframe for display),
|
||||
but here I am using my explicit mapping instead.
|
||||
|
||||
The implementation works already with two additional lines and a loop,
|
||||
by looping through our keys and adding a new layer for each one,
|
||||
filtering out all the rows which do not exactly match the key using a pandas filter.
|
||||
|
||||
```python
|
||||
m = folium.Map(tiles="cartodb positron")
|
||||
for country in country_colors.keys():
|
||||
fg = folium.FeatureGroup(name=country, show=True).add_to(m)
|
||||
folium.GeoJson(
|
||||
gdf[gdf["country"] == country],
|
||||
name="Nuclear Explosions",
|
||||
marker=folium.Circle(radius=3, fill_opacity=0.4),
|
||||
style_function=lambda x: {
|
||||
"color": rgb_to_hex(country_colors[x["properties"]["country"]]),
|
||||
"radius": (
|
||||
x["properties"]["magnitude_body"]
|
||||
if x["properties"]["magnitude_body"] > 0
|
||||
else 1.0
|
||||
)
|
||||
* 10,
|
||||
},
|
||||
).add_to(fg)
|
||||
folium.LayerControl().add_to(m)
|
||||
```
|
||||
|
||||
## Remaining issues
|
||||
|
||||
While working with polars is wonderful and seaborn takes a lot of the stress of creating half-way nicely formatted plots out of mind while first creating them,
|
||||
some pain points remain.
|
||||
|
||||
While I am cautiously optimistic, seaborn's 'objects-style' interface still remains woefully undercooked.
|
||||
It is already possible to create some basic plots with it and its declarative style is wonderful
|
||||
(as in, it really matches the mental model I have of drawing individual plot elements into a coherent whole).
|
||||
But for anything more complex --- which in my opinion is exactly where this interface will really shine ---
|
||||
it remains out of reach because of missing methods and implementations.
|
||||
This is, of course, ideally just a temporary issue until the implementation gets better,
|
||||
but until then we are still stuck with the more strange mish-mash of seaborn simplicity with matplotlib exactness,
|
||||
and having to know when to leave the former behind and delve into the arcane API of the latter.
|
||||
|
||||
Additionally, when combined with quarto for publishing some more pain points appear.
|
||||
One that has been true for the longest time, and will likely remain so for the foreseeable future,
|
||||
is that tables beyond a certain complexity are just _painful_ in quarto multi-output publishing.
|
||||
This project made use the fantastic python library [great tables]() which indeed lives up to its name and produces absolutely great tables with very little effort.
|
||||
However, it primarily targets the html format.
|
||||
Getting this format into shape for quarto to then translate it into the pandoc AST and ultimately whatever format is not pretty.
|
||||
For example LaTeX routinely just crashes instead of rendering the table correctly into a PDF file.
|
262
poetry.lock
generated
262
poetry.lock
generated
|
@ -2028,6 +2028,20 @@ files = [
|
|||
{file = "numpy-2.0.0.tar.gz", hash = "sha256:cf5d1c9e6837f8af9f92b6bd3e86d513cdc11f60fd62185cc49ec7d1aba34864"},
|
||||
]
|
||||
|
||||
[[package]]
|
||||
name = "outcome"
|
||||
version = "1.3.0.post0"
|
||||
description = "Capture the outcome of Python function calls."
|
||||
optional = false
|
||||
python-versions = ">=3.7"
|
||||
files = [
|
||||
{file = "outcome-1.3.0.post0-py2.py3-none-any.whl", hash = "sha256:e771c5ce06d1415e356078d3bdd68523f284b4ce5419828922b6871e65eda82b"},
|
||||
{file = "outcome-1.3.0.post0.tar.gz", hash = "sha256:9dcf02e65f2971b80047b377468e72a268e15c0af3cf1238e6ff14f7f91143b8"},
|
||||
]
|
||||
|
||||
[package.dependencies]
|
||||
attrs = ">=19.2.0"
|
||||
|
||||
[[package]]
|
||||
name = "overrides"
|
||||
version = "7.7.0"
|
||||
|
@ -2161,84 +2175,95 @@ ptyprocess = ">=0.5"
|
|||
|
||||
[[package]]
|
||||
name = "pillow"
|
||||
version = "10.3.0"
|
||||
version = "10.4.0"
|
||||
description = "Python Imaging Library (Fork)"
|
||||
optional = false
|
||||
python-versions = ">=3.8"
|
||||
files = [
|
||||
{file = "pillow-10.3.0-cp310-cp310-macosx_10_10_x86_64.whl", hash = "sha256:90b9e29824800e90c84e4022dd5cc16eb2d9605ee13f05d47641eb183cd73d45"},
|
||||
{file = "pillow-10.3.0-cp310-cp310-macosx_11_0_arm64.whl", hash = "sha256:a2c405445c79c3f5a124573a051062300936b0281fee57637e706453e452746c"},
|
||||
{file = "pillow-10.3.0-cp310-cp310-manylinux_2_17_aarch64.manylinux2014_aarch64.whl", hash = "sha256:78618cdbccaa74d3f88d0ad6cb8ac3007f1a6fa5c6f19af64b55ca170bfa1edf"},
|
||||
{file = "pillow-10.3.0-cp310-cp310-manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:261ddb7ca91fcf71757979534fb4c128448b5b4c55cb6152d280312062f69599"},
|
||||
{file = "pillow-10.3.0-cp310-cp310-manylinux_2_28_aarch64.whl", hash = "sha256:ce49c67f4ea0609933d01c0731b34b8695a7a748d6c8d186f95e7d085d2fe475"},
|
||||
{file = "pillow-10.3.0-cp310-cp310-manylinux_2_28_x86_64.whl", hash = "sha256:b14f16f94cbc61215115b9b1236f9c18403c15dd3c52cf629072afa9d54c1cbf"},
|
||||
{file = "pillow-10.3.0-cp310-cp310-musllinux_1_1_aarch64.whl", hash = "sha256:d33891be6df59d93df4d846640f0e46f1a807339f09e79a8040bc887bdcd7ed3"},
|
||||
{file = "pillow-10.3.0-cp310-cp310-musllinux_1_1_x86_64.whl", hash = "sha256:b50811d664d392f02f7761621303eba9d1b056fb1868c8cdf4231279645c25f5"},
|
||||
{file = "pillow-10.3.0-cp310-cp310-win32.whl", hash = "sha256:ca2870d5d10d8726a27396d3ca4cf7976cec0f3cb706debe88e3a5bd4610f7d2"},
|
||||
{file = "pillow-10.3.0-cp310-cp310-win_amd64.whl", hash = "sha256:f0d0591a0aeaefdaf9a5e545e7485f89910c977087e7de2b6c388aec32011e9f"},
|
||||
{file = "pillow-10.3.0-cp310-cp310-win_arm64.whl", hash = "sha256:ccce24b7ad89adb5a1e34a6ba96ac2530046763912806ad4c247356a8f33a67b"},
|
||||
{file = "pillow-10.3.0-cp311-cp311-macosx_10_10_x86_64.whl", hash = "sha256:5f77cf66e96ae734717d341c145c5949c63180842a545c47a0ce7ae52ca83795"},
|
||||
{file = "pillow-10.3.0-cp311-cp311-macosx_11_0_arm64.whl", hash = "sha256:e4b878386c4bf293578b48fc570b84ecfe477d3b77ba39a6e87150af77f40c57"},
|
||||
{file = "pillow-10.3.0-cp311-cp311-manylinux_2_17_aarch64.manylinux2014_aarch64.whl", hash = "sha256:fdcbb4068117dfd9ce0138d068ac512843c52295ed996ae6dd1faf537b6dbc27"},
|
||||
{file = "pillow-10.3.0-cp311-cp311-manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:9797a6c8fe16f25749b371c02e2ade0efb51155e767a971c61734b1bf6293994"},
|
||||
{file = "pillow-10.3.0-cp311-cp311-manylinux_2_28_aarch64.whl", hash = "sha256:9e91179a242bbc99be65e139e30690e081fe6cb91a8e77faf4c409653de39451"},
|
||||
{file = "pillow-10.3.0-cp311-cp311-manylinux_2_28_x86_64.whl", hash = "sha256:1b87bd9d81d179bd8ab871603bd80d8645729939f90b71e62914e816a76fc6bd"},
|
||||
{file = "pillow-10.3.0-cp311-cp311-musllinux_1_1_aarch64.whl", hash = "sha256:81d09caa7b27ef4e61cb7d8fbf1714f5aec1c6b6c5270ee53504981e6e9121ad"},
|
||||
{file = "pillow-10.3.0-cp311-cp311-musllinux_1_1_x86_64.whl", hash = "sha256:048ad577748b9fa4a99a0548c64f2cb8d672d5bf2e643a739ac8faff1164238c"},
|
||||
{file = "pillow-10.3.0-cp311-cp311-win32.whl", hash = "sha256:7161ec49ef0800947dc5570f86568a7bb36fa97dd09e9827dc02b718c5643f09"},
|
||||
{file = "pillow-10.3.0-cp311-cp311-win_amd64.whl", hash = "sha256:8eb0908e954d093b02a543dc963984d6e99ad2b5e36503d8a0aaf040505f747d"},
|
||||
{file = "pillow-10.3.0-cp311-cp311-win_arm64.whl", hash = "sha256:4e6f7d1c414191c1199f8996d3f2282b9ebea0945693fb67392c75a3a320941f"},
|
||||
{file = "pillow-10.3.0-cp312-cp312-macosx_10_10_x86_64.whl", hash = "sha256:e46f38133e5a060d46bd630faa4d9fa0202377495df1f068a8299fd78c84de84"},
|
||||
{file = "pillow-10.3.0-cp312-cp312-macosx_11_0_arm64.whl", hash = "sha256:50b8eae8f7334ec826d6eeffaeeb00e36b5e24aa0b9df322c247539714c6df19"},
|
||||
{file = "pillow-10.3.0-cp312-cp312-manylinux_2_17_aarch64.manylinux2014_aarch64.whl", hash = "sha256:9d3bea1c75f8c53ee4d505c3e67d8c158ad4df0d83170605b50b64025917f338"},
|
||||
{file = "pillow-10.3.0-cp312-cp312-manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:19aeb96d43902f0a783946a0a87dbdad5c84c936025b8419da0a0cd7724356b1"},
|
||||
{file = "pillow-10.3.0-cp312-cp312-manylinux_2_28_aarch64.whl", hash = "sha256:74d28c17412d9caa1066f7a31df8403ec23d5268ba46cd0ad2c50fb82ae40462"},
|
||||
{file = "pillow-10.3.0-cp312-cp312-manylinux_2_28_x86_64.whl", hash = "sha256:ff61bfd9253c3915e6d41c651d5f962da23eda633cf02262990094a18a55371a"},
|
||||
{file = "pillow-10.3.0-cp312-cp312-musllinux_1_1_aarch64.whl", hash = "sha256:d886f5d353333b4771d21267c7ecc75b710f1a73d72d03ca06df49b09015a9ef"},
|
||||
{file = "pillow-10.3.0-cp312-cp312-musllinux_1_1_x86_64.whl", hash = "sha256:4b5ec25d8b17217d635f8935dbc1b9aa5907962fae29dff220f2659487891cd3"},
|
||||
{file = "pillow-10.3.0-cp312-cp312-win32.whl", hash = "sha256:51243f1ed5161b9945011a7360e997729776f6e5d7005ba0c6879267d4c5139d"},
|
||||
{file = "pillow-10.3.0-cp312-cp312-win_amd64.whl", hash = "sha256:412444afb8c4c7a6cc11a47dade32982439925537e483be7c0ae0cf96c4f6a0b"},
|
||||
{file = "pillow-10.3.0-cp312-cp312-win_arm64.whl", hash = "sha256:798232c92e7665fe82ac085f9d8e8ca98826f8e27859d9a96b41d519ecd2e49a"},
|
||||
{file = "pillow-10.3.0-cp38-cp38-macosx_10_10_x86_64.whl", hash = "sha256:4eaa22f0d22b1a7e93ff0a596d57fdede2e550aecffb5a1ef1106aaece48e96b"},
|
||||
{file = "pillow-10.3.0-cp38-cp38-macosx_11_0_arm64.whl", hash = "sha256:cd5e14fbf22a87321b24c88669aad3a51ec052eb145315b3da3b7e3cc105b9a2"},
|
||||
{file = "pillow-10.3.0-cp38-cp38-manylinux_2_17_aarch64.manylinux2014_aarch64.whl", hash = "sha256:1530e8f3a4b965eb6a7785cf17a426c779333eb62c9a7d1bbcf3ffd5bf77a4aa"},
|
||||
{file = "pillow-10.3.0-cp38-cp38-manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:5d512aafa1d32efa014fa041d38868fda85028e3f930a96f85d49c7d8ddc0383"},
|
||||
{file = "pillow-10.3.0-cp38-cp38-manylinux_2_28_aarch64.whl", hash = "sha256:339894035d0ede518b16073bdc2feef4c991ee991a29774b33e515f1d308e08d"},
|
||||
{file = "pillow-10.3.0-cp38-cp38-manylinux_2_28_x86_64.whl", hash = "sha256:aa7e402ce11f0885305bfb6afb3434b3cd8f53b563ac065452d9d5654c7b86fd"},
|
||||
{file = "pillow-10.3.0-cp38-cp38-musllinux_1_1_aarch64.whl", hash = "sha256:0ea2a783a2bdf2a561808fe4a7a12e9aa3799b701ba305de596bc48b8bdfce9d"},
|
||||
{file = "pillow-10.3.0-cp38-cp38-musllinux_1_1_x86_64.whl", hash = "sha256:c78e1b00a87ce43bb37642c0812315b411e856a905d58d597750eb79802aaaa3"},
|
||||
{file = "pillow-10.3.0-cp38-cp38-win32.whl", hash = "sha256:72d622d262e463dfb7595202d229f5f3ab4b852289a1cd09650362db23b9eb0b"},
|
||||
{file = "pillow-10.3.0-cp38-cp38-win_amd64.whl", hash = "sha256:2034f6759a722da3a3dbd91a81148cf884e91d1b747992ca288ab88c1de15999"},
|
||||
{file = "pillow-10.3.0-cp39-cp39-macosx_10_10_x86_64.whl", hash = "sha256:2ed854e716a89b1afcedea551cd85f2eb2a807613752ab997b9974aaa0d56936"},
|
||||
{file = "pillow-10.3.0-cp39-cp39-macosx_11_0_arm64.whl", hash = "sha256:dc1a390a82755a8c26c9964d457d4c9cbec5405896cba94cf51f36ea0d855002"},
|
||||
{file = "pillow-10.3.0-cp39-cp39-manylinux_2_17_aarch64.manylinux2014_aarch64.whl", hash = "sha256:4203efca580f0dd6f882ca211f923168548f7ba334c189e9eab1178ab840bf60"},
|
||||
{file = "pillow-10.3.0-cp39-cp39-manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:3102045a10945173d38336f6e71a8dc71bcaeed55c3123ad4af82c52807b9375"},
|
||||
{file = "pillow-10.3.0-cp39-cp39-manylinux_2_28_aarch64.whl", hash = "sha256:6fb1b30043271ec92dc65f6d9f0b7a830c210b8a96423074b15c7bc999975f57"},
|
||||
{file = "pillow-10.3.0-cp39-cp39-manylinux_2_28_x86_64.whl", hash = "sha256:1dfc94946bc60ea375cc39cff0b8da6c7e5f8fcdc1d946beb8da5c216156ddd8"},
|
||||
{file = "pillow-10.3.0-cp39-cp39-musllinux_1_1_aarch64.whl", hash = "sha256:b09b86b27a064c9624d0a6c54da01c1beaf5b6cadfa609cf63789b1d08a797b9"},
|
||||
{file = "pillow-10.3.0-cp39-cp39-musllinux_1_1_x86_64.whl", hash = "sha256:d3b2348a78bc939b4fed6552abfd2e7988e0f81443ef3911a4b8498ca084f6eb"},
|
||||
{file = "pillow-10.3.0-cp39-cp39-win32.whl", hash = "sha256:45ebc7b45406febf07fef35d856f0293a92e7417ae7933207e90bf9090b70572"},
|
||||
{file = "pillow-10.3.0-cp39-cp39-win_amd64.whl", hash = "sha256:0ba26351b137ca4e0db0342d5d00d2e355eb29372c05afd544ebf47c0956ffeb"},
|
||||
{file = "pillow-10.3.0-cp39-cp39-win_arm64.whl", hash = "sha256:50fd3f6b26e3441ae07b7c979309638b72abc1a25da31a81a7fbd9495713ef4f"},
|
||||
{file = "pillow-10.3.0-pp310-pypy310_pp73-macosx_10_10_x86_64.whl", hash = "sha256:6b02471b72526ab8a18c39cb7967b72d194ec53c1fd0a70b050565a0f366d355"},
|
||||
{file = "pillow-10.3.0-pp310-pypy310_pp73-macosx_11_0_arm64.whl", hash = "sha256:8ab74c06ffdab957d7670c2a5a6e1a70181cd10b727cd788c4dd9005b6a8acd9"},
|
||||
{file = "pillow-10.3.0-pp310-pypy310_pp73-manylinux_2_17_aarch64.manylinux2014_aarch64.whl", hash = "sha256:048eeade4c33fdf7e08da40ef402e748df113fd0b4584e32c4af74fe78baaeb2"},
|
||||
{file = "pillow-10.3.0-pp310-pypy310_pp73-manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:9e2ec1e921fd07c7cda7962bad283acc2f2a9ccc1b971ee4b216b75fad6f0463"},
|
||||
{file = "pillow-10.3.0-pp310-pypy310_pp73-manylinux_2_28_aarch64.whl", hash = "sha256:4c8e73e99da7db1b4cad7f8d682cf6abad7844da39834c288fbfa394a47bbced"},
|
||||
{file = "pillow-10.3.0-pp310-pypy310_pp73-manylinux_2_28_x86_64.whl", hash = "sha256:16563993329b79513f59142a6b02055e10514c1a8e86dca8b48a893e33cf91e3"},
|
||||
{file = "pillow-10.3.0-pp310-pypy310_pp73-win_amd64.whl", hash = "sha256:dd78700f5788ae180b5ee8902c6aea5a5726bac7c364b202b4b3e3ba2d293170"},
|
||||
{file = "pillow-10.3.0-pp39-pypy39_pp73-macosx_10_10_x86_64.whl", hash = "sha256:aff76a55a8aa8364d25400a210a65ff59d0168e0b4285ba6bf2bd83cf675ba32"},
|
||||
{file = "pillow-10.3.0-pp39-pypy39_pp73-macosx_11_0_arm64.whl", hash = "sha256:b7bc2176354defba3edc2b9a777744462da2f8e921fbaf61e52acb95bafa9828"},
|
||||
{file = "pillow-10.3.0-pp39-pypy39_pp73-manylinux_2_17_aarch64.manylinux2014_aarch64.whl", hash = "sha256:793b4e24db2e8742ca6423d3fde8396db336698c55cd34b660663ee9e45ed37f"},
|
||||
{file = "pillow-10.3.0-pp39-pypy39_pp73-manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:d93480005693d247f8346bc8ee28c72a2191bdf1f6b5db469c096c0c867ac015"},
|
||||
{file = "pillow-10.3.0-pp39-pypy39_pp73-manylinux_2_28_aarch64.whl", hash = "sha256:c83341b89884e2b2e55886e8fbbf37c3fa5efd6c8907124aeb72f285ae5696e5"},
|
||||
{file = "pillow-10.3.0-pp39-pypy39_pp73-manylinux_2_28_x86_64.whl", hash = "sha256:1a1d1915db1a4fdb2754b9de292642a39a7fb28f1736699527bb649484fb966a"},
|
||||
{file = "pillow-10.3.0-pp39-pypy39_pp73-win_amd64.whl", hash = "sha256:a0eaa93d054751ee9964afa21c06247779b90440ca41d184aeb5d410f20ff591"},
|
||||
{file = "pillow-10.3.0.tar.gz", hash = "sha256:9d2455fbf44c914840c793e89aa82d0e1763a14253a000743719ae5946814b2d"},
|
||||
{file = "pillow-10.4.0-cp310-cp310-macosx_10_10_x86_64.whl", hash = "sha256:4d9667937cfa347525b319ae34375c37b9ee6b525440f3ef48542fcf66f2731e"},
|
||||
{file = "pillow-10.4.0-cp310-cp310-macosx_11_0_arm64.whl", hash = "sha256:543f3dc61c18dafb755773efc89aae60d06b6596a63914107f75459cf984164d"},
|
||||
{file = "pillow-10.4.0-cp310-cp310-manylinux_2_17_aarch64.manylinux2014_aarch64.whl", hash = "sha256:7928ecbf1ece13956b95d9cbcfc77137652b02763ba384d9ab508099a2eca856"},
|
||||
{file = "pillow-10.4.0-cp310-cp310-manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:e4d49b85c4348ea0b31ea63bc75a9f3857869174e2bf17e7aba02945cd218e6f"},
|
||||
{file = "pillow-10.4.0-cp310-cp310-manylinux_2_28_aarch64.whl", hash = "sha256:6c762a5b0997f5659a5ef2266abc1d8851ad7749ad9a6a5506eb23d314e4f46b"},
|
||||
{file = "pillow-10.4.0-cp310-cp310-manylinux_2_28_x86_64.whl", hash = "sha256:a985e028fc183bf12a77a8bbf36318db4238a3ded7fa9df1b9a133f1cb79f8fc"},
|
||||
{file = "pillow-10.4.0-cp310-cp310-musllinux_1_2_aarch64.whl", hash = "sha256:812f7342b0eee081eaec84d91423d1b4650bb9828eb53d8511bcef8ce5aecf1e"},
|
||||
{file = "pillow-10.4.0-cp310-cp310-musllinux_1_2_x86_64.whl", hash = "sha256:ac1452d2fbe4978c2eec89fb5a23b8387aba707ac72810d9490118817d9c0b46"},
|
||||
{file = "pillow-10.4.0-cp310-cp310-win32.whl", hash = "sha256:bcd5e41a859bf2e84fdc42f4edb7d9aba0a13d29a2abadccafad99de3feff984"},
|
||||
{file = "pillow-10.4.0-cp310-cp310-win_amd64.whl", hash = "sha256:ecd85a8d3e79cd7158dec1c9e5808e821feea088e2f69a974db5edf84dc53141"},
|
||||
{file = "pillow-10.4.0-cp310-cp310-win_arm64.whl", hash = "sha256:ff337c552345e95702c5fde3158acb0625111017d0e5f24bf3acdb9cc16b90d1"},
|
||||
{file = "pillow-10.4.0-cp311-cp311-macosx_10_10_x86_64.whl", hash = "sha256:0a9ec697746f268507404647e531e92889890a087e03681a3606d9b920fbee3c"},
|
||||
{file = "pillow-10.4.0-cp311-cp311-macosx_11_0_arm64.whl", hash = "sha256:dfe91cb65544a1321e631e696759491ae04a2ea11d36715eca01ce07284738be"},
|
||||
{file = "pillow-10.4.0-cp311-cp311-manylinux_2_17_aarch64.manylinux2014_aarch64.whl", hash = "sha256:5dc6761a6efc781e6a1544206f22c80c3af4c8cf461206d46a1e6006e4429ff3"},
|
||||
{file = "pillow-10.4.0-cp311-cp311-manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:5e84b6cc6a4a3d76c153a6b19270b3526a5a8ed6b09501d3af891daa2a9de7d6"},
|
||||
{file = "pillow-10.4.0-cp311-cp311-manylinux_2_28_aarch64.whl", hash = "sha256:bbc527b519bd3aa9d7f429d152fea69f9ad37c95f0b02aebddff592688998abe"},
|
||||
{file = "pillow-10.4.0-cp311-cp311-manylinux_2_28_x86_64.whl", hash = "sha256:76a911dfe51a36041f2e756b00f96ed84677cdeb75d25c767f296c1c1eda1319"},
|
||||
{file = "pillow-10.4.0-cp311-cp311-musllinux_1_2_aarch64.whl", hash = "sha256:59291fb29317122398786c2d44427bbd1a6d7ff54017075b22be9d21aa59bd8d"},
|
||||
{file = "pillow-10.4.0-cp311-cp311-musllinux_1_2_x86_64.whl", hash = "sha256:416d3a5d0e8cfe4f27f574362435bc9bae57f679a7158e0096ad2beb427b8696"},
|
||||
{file = "pillow-10.4.0-cp311-cp311-win32.whl", hash = "sha256:7086cc1d5eebb91ad24ded9f58bec6c688e9f0ed7eb3dbbf1e4800280a896496"},
|
||||
{file = "pillow-10.4.0-cp311-cp311-win_amd64.whl", hash = "sha256:cbed61494057c0f83b83eb3a310f0bf774b09513307c434d4366ed64f4128a91"},
|
||||
{file = "pillow-10.4.0-cp311-cp311-win_arm64.whl", hash = "sha256:f5f0c3e969c8f12dd2bb7e0b15d5c468b51e5017e01e2e867335c81903046a22"},
|
||||
{file = "pillow-10.4.0-cp312-cp312-macosx_10_10_x86_64.whl", hash = "sha256:673655af3eadf4df6b5457033f086e90299fdd7a47983a13827acf7459c15d94"},
|
||||
{file = "pillow-10.4.0-cp312-cp312-macosx_11_0_arm64.whl", hash = "sha256:866b6942a92f56300012f5fbac71f2d610312ee65e22f1aa2609e491284e5597"},
|
||||
{file = "pillow-10.4.0-cp312-cp312-manylinux_2_17_aarch64.manylinux2014_aarch64.whl", hash = "sha256:29dbdc4207642ea6aad70fbde1a9338753d33fb23ed6956e706936706f52dd80"},
|
||||
{file = "pillow-10.4.0-cp312-cp312-manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:bf2342ac639c4cf38799a44950bbc2dfcb685f052b9e262f446482afaf4bffca"},
|
||||
{file = "pillow-10.4.0-cp312-cp312-manylinux_2_28_aarch64.whl", hash = "sha256:f5b92f4d70791b4a67157321c4e8225d60b119c5cc9aee8ecf153aace4aad4ef"},
|
||||
{file = "pillow-10.4.0-cp312-cp312-manylinux_2_28_x86_64.whl", hash = "sha256:86dcb5a1eb778d8b25659d5e4341269e8590ad6b4e8b44d9f4b07f8d136c414a"},
|
||||
{file = "pillow-10.4.0-cp312-cp312-musllinux_1_2_aarch64.whl", hash = "sha256:780c072c2e11c9b2c7ca37f9a2ee8ba66f44367ac3e5c7832afcfe5104fd6d1b"},
|
||||
{file = "pillow-10.4.0-cp312-cp312-musllinux_1_2_x86_64.whl", hash = "sha256:37fb69d905be665f68f28a8bba3c6d3223c8efe1edf14cc4cfa06c241f8c81d9"},
|
||||
{file = "pillow-10.4.0-cp312-cp312-win32.whl", hash = "sha256:7dfecdbad5c301d7b5bde160150b4db4c659cee2b69589705b6f8a0c509d9f42"},
|
||||
{file = "pillow-10.4.0-cp312-cp312-win_amd64.whl", hash = "sha256:1d846aea995ad352d4bdcc847535bd56e0fd88d36829d2c90be880ef1ee4668a"},
|
||||
{file = "pillow-10.4.0-cp312-cp312-win_arm64.whl", hash = "sha256:e553cad5179a66ba15bb18b353a19020e73a7921296a7979c4a2b7f6a5cd57f9"},
|
||||
{file = "pillow-10.4.0-cp313-cp313-macosx_10_13_x86_64.whl", hash = "sha256:8bc1a764ed8c957a2e9cacf97c8b2b053b70307cf2996aafd70e91a082e70df3"},
|
||||
{file = "pillow-10.4.0-cp313-cp313-macosx_11_0_arm64.whl", hash = "sha256:6209bb41dc692ddfee4942517c19ee81b86c864b626dbfca272ec0f7cff5d9fb"},
|
||||
{file = "pillow-10.4.0-cp313-cp313-manylinux_2_17_aarch64.manylinux2014_aarch64.whl", hash = "sha256:bee197b30783295d2eb680b311af15a20a8b24024a19c3a26431ff83eb8d1f70"},
|
||||
{file = "pillow-10.4.0-cp313-cp313-manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:1ef61f5dd14c300786318482456481463b9d6b91ebe5ef12f405afbba77ed0be"},
|
||||
{file = "pillow-10.4.0-cp313-cp313-manylinux_2_28_aarch64.whl", hash = "sha256:297e388da6e248c98bc4a02e018966af0c5f92dfacf5a5ca22fa01cb3179bca0"},
|
||||
{file = "pillow-10.4.0-cp313-cp313-manylinux_2_28_x86_64.whl", hash = "sha256:e4db64794ccdf6cb83a59d73405f63adbe2a1887012e308828596100a0b2f6cc"},
|
||||
{file = "pillow-10.4.0-cp313-cp313-musllinux_1_2_aarch64.whl", hash = "sha256:bd2880a07482090a3bcb01f4265f1936a903d70bc740bfcb1fd4e8a2ffe5cf5a"},
|
||||
{file = "pillow-10.4.0-cp313-cp313-musllinux_1_2_x86_64.whl", hash = "sha256:4b35b21b819ac1dbd1233317adeecd63495f6babf21b7b2512d244ff6c6ce309"},
|
||||
{file = "pillow-10.4.0-cp313-cp313-win32.whl", hash = "sha256:551d3fd6e9dc15e4c1eb6fc4ba2b39c0c7933fa113b220057a34f4bb3268a060"},
|
||||
{file = "pillow-10.4.0-cp313-cp313-win_amd64.whl", hash = "sha256:030abdbe43ee02e0de642aee345efa443740aa4d828bfe8e2eb11922ea6a21ea"},
|
||||
{file = "pillow-10.4.0-cp313-cp313-win_arm64.whl", hash = "sha256:5b001114dd152cfd6b23befeb28d7aee43553e2402c9f159807bf55f33af8a8d"},
|
||||
{file = "pillow-10.4.0-cp38-cp38-macosx_10_10_x86_64.whl", hash = "sha256:8d4d5063501b6dd4024b8ac2f04962d661222d120381272deea52e3fc52d3736"},
|
||||
{file = "pillow-10.4.0-cp38-cp38-macosx_11_0_arm64.whl", hash = "sha256:7c1ee6f42250df403c5f103cbd2768a28fe1a0ea1f0f03fe151c8741e1469c8b"},
|
||||
{file = "pillow-10.4.0-cp38-cp38-manylinux_2_17_aarch64.manylinux2014_aarch64.whl", hash = "sha256:b15e02e9bb4c21e39876698abf233c8c579127986f8207200bc8a8f6bb27acf2"},
|
||||
{file = "pillow-10.4.0-cp38-cp38-manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:7a8d4bade9952ea9a77d0c3e49cbd8b2890a399422258a77f357b9cc9be8d680"},
|
||||
{file = "pillow-10.4.0-cp38-cp38-manylinux_2_28_aarch64.whl", hash = "sha256:43efea75eb06b95d1631cb784aa40156177bf9dd5b4b03ff38979e048258bc6b"},
|
||||
{file = "pillow-10.4.0-cp38-cp38-manylinux_2_28_x86_64.whl", hash = "sha256:950be4d8ba92aca4b2bb0741285a46bfae3ca699ef913ec8416c1b78eadd64cd"},
|
||||
{file = "pillow-10.4.0-cp38-cp38-musllinux_1_2_aarch64.whl", hash = "sha256:d7480af14364494365e89d6fddc510a13e5a2c3584cb19ef65415ca57252fb84"},
|
||||
{file = "pillow-10.4.0-cp38-cp38-musllinux_1_2_x86_64.whl", hash = "sha256:73664fe514b34c8f02452ffb73b7a92c6774e39a647087f83d67f010eb9a0cf0"},
|
||||
{file = "pillow-10.4.0-cp38-cp38-win32.whl", hash = "sha256:e88d5e6ad0d026fba7bdab8c3f225a69f063f116462c49892b0149e21b6c0a0e"},
|
||||
{file = "pillow-10.4.0-cp38-cp38-win_amd64.whl", hash = "sha256:5161eef006d335e46895297f642341111945e2c1c899eb406882a6c61a4357ab"},
|
||||
{file = "pillow-10.4.0-cp39-cp39-macosx_10_10_x86_64.whl", hash = "sha256:0ae24a547e8b711ccaaf99c9ae3cd975470e1a30caa80a6aaee9a2f19c05701d"},
|
||||
{file = "pillow-10.4.0-cp39-cp39-macosx_11_0_arm64.whl", hash = "sha256:298478fe4f77a4408895605f3482b6cc6222c018b2ce565c2b6b9c354ac3229b"},
|
||||
{file = "pillow-10.4.0-cp39-cp39-manylinux_2_17_aarch64.manylinux2014_aarch64.whl", hash = "sha256:134ace6dc392116566980ee7436477d844520a26a4b1bd4053f6f47d096997fd"},
|
||||
{file = "pillow-10.4.0-cp39-cp39-manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:930044bb7679ab003b14023138b50181899da3f25de50e9dbee23b61b4de2126"},
|
||||
{file = "pillow-10.4.0-cp39-cp39-manylinux_2_28_aarch64.whl", hash = "sha256:c76e5786951e72ed3686e122d14c5d7012f16c8303a674d18cdcd6d89557fc5b"},
|
||||
{file = "pillow-10.4.0-cp39-cp39-manylinux_2_28_x86_64.whl", hash = "sha256:b2724fdb354a868ddf9a880cb84d102da914e99119211ef7ecbdc613b8c96b3c"},
|
||||
{file = "pillow-10.4.0-cp39-cp39-musllinux_1_2_aarch64.whl", hash = "sha256:dbc6ae66518ab3c5847659e9988c3b60dc94ffb48ef9168656e0019a93dbf8a1"},
|
||||
{file = "pillow-10.4.0-cp39-cp39-musllinux_1_2_x86_64.whl", hash = "sha256:06b2f7898047ae93fad74467ec3d28fe84f7831370e3c258afa533f81ef7f3df"},
|
||||
{file = "pillow-10.4.0-cp39-cp39-win32.whl", hash = "sha256:7970285ab628a3779aecc35823296a7869f889b8329c16ad5a71e4901a3dc4ef"},
|
||||
{file = "pillow-10.4.0-cp39-cp39-win_amd64.whl", hash = "sha256:961a7293b2457b405967af9c77dcaa43cc1a8cd50d23c532e62d48ab6cdd56f5"},
|
||||
{file = "pillow-10.4.0-cp39-cp39-win_arm64.whl", hash = "sha256:32cda9e3d601a52baccb2856b8ea1fc213c90b340c542dcef77140dfa3278a9e"},
|
||||
{file = "pillow-10.4.0-pp310-pypy310_pp73-macosx_10_15_x86_64.whl", hash = "sha256:5b4815f2e65b30f5fbae9dfffa8636d992d49705723fe86a3661806e069352d4"},
|
||||
{file = "pillow-10.4.0-pp310-pypy310_pp73-macosx_11_0_arm64.whl", hash = "sha256:8f0aef4ef59694b12cadee839e2ba6afeab89c0f39a3adc02ed51d109117b8da"},
|
||||
{file = "pillow-10.4.0-pp310-pypy310_pp73-manylinux_2_17_aarch64.manylinux2014_aarch64.whl", hash = "sha256:9f4727572e2918acaa9077c919cbbeb73bd2b3ebcfe033b72f858fc9fbef0026"},
|
||||
{file = "pillow-10.4.0-pp310-pypy310_pp73-manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:ff25afb18123cea58a591ea0244b92eb1e61a1fd497bf6d6384f09bc3262ec3e"},
|
||||
{file = "pillow-10.4.0-pp310-pypy310_pp73-manylinux_2_28_aarch64.whl", hash = "sha256:dc3e2db6ba09ffd7d02ae9141cfa0ae23393ee7687248d46a7507b75d610f4f5"},
|
||||
{file = "pillow-10.4.0-pp310-pypy310_pp73-manylinux_2_28_x86_64.whl", hash = "sha256:02a2be69f9c9b8c1e97cf2713e789d4e398c751ecfd9967c18d0ce304efbf885"},
|
||||
{file = "pillow-10.4.0-pp310-pypy310_pp73-win_amd64.whl", hash = "sha256:0755ffd4a0c6f267cccbae2e9903d95477ca2f77c4fcf3a3a09570001856c8a5"},
|
||||
{file = "pillow-10.4.0-pp39-pypy39_pp73-macosx_10_15_x86_64.whl", hash = "sha256:a02364621fe369e06200d4a16558e056fe2805d3468350df3aef21e00d26214b"},
|
||||
{file = "pillow-10.4.0-pp39-pypy39_pp73-macosx_11_0_arm64.whl", hash = "sha256:1b5dea9831a90e9d0721ec417a80d4cbd7022093ac38a568db2dd78363b00908"},
|
||||
{file = "pillow-10.4.0-pp39-pypy39_pp73-manylinux_2_17_aarch64.manylinux2014_aarch64.whl", hash = "sha256:9b885f89040bb8c4a1573566bbb2f44f5c505ef6e74cec7ab9068c900047f04b"},
|
||||
{file = "pillow-10.4.0-pp39-pypy39_pp73-manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:87dd88ded2e6d74d31e1e0a99a726a6765cda32d00ba72dc37f0651f306daaa8"},
|
||||
{file = "pillow-10.4.0-pp39-pypy39_pp73-manylinux_2_28_aarch64.whl", hash = "sha256:2db98790afc70118bd0255c2eeb465e9767ecf1f3c25f9a1abb8ffc8cfd1fe0a"},
|
||||
{file = "pillow-10.4.0-pp39-pypy39_pp73-manylinux_2_28_x86_64.whl", hash = "sha256:f7baece4ce06bade126fb84b8af1c33439a76d8a6fd818970215e0560ca28c27"},
|
||||
{file = "pillow-10.4.0-pp39-pypy39_pp73-win_amd64.whl", hash = "sha256:cfdd747216947628af7b259d274771d84db2268ca062dd5faf373639d00113a3"},
|
||||
{file = "pillow-10.4.0.tar.gz", hash = "sha256:166c1cd4d24309b30d61f79f4a9114b7b2313d7450912277855ff5dfd7cd4a06"},
|
||||
]
|
||||
|
||||
[package.extras]
|
||||
docs = ["furo", "olefile", "sphinx (>=2.4)", "sphinx-copybutton", "sphinx-inline-tabs", "sphinx-removed-in", "sphinxext-opengraph"]
|
||||
docs = ["furo", "olefile", "sphinx (>=7.3)", "sphinx-copybutton", "sphinx-inline-tabs", "sphinxext-opengraph"]
|
||||
fpx = ["olefile"]
|
||||
mic = ["olefile"]
|
||||
tests = ["check-manifest", "coverage", "defusedxml", "markdown2", "olefile", "packaging", "pyroma", "pytest", "pytest-cov", "pytest-timeout"]
|
||||
|
@ -2563,6 +2588,18 @@ files = [
|
|||
[package.dependencies]
|
||||
certifi = "*"
|
||||
|
||||
[[package]]
|
||||
name = "pysocks"
|
||||
version = "1.7.1"
|
||||
description = "A Python SOCKS client module. See https://github.com/Anorov/PySocks for more information."
|
||||
optional = false
|
||||
python-versions = ">=2.7, !=3.0.*, !=3.1.*, !=3.2.*, !=3.3.*"
|
||||
files = [
|
||||
{file = "PySocks-1.7.1-py27-none-any.whl", hash = "sha256:08e69f092cc6dbe92a0fdd16eeb9b9ffbc13cadfe5ca4c7bd92ffb078b293299"},
|
||||
{file = "PySocks-1.7.1-py3-none-any.whl", hash = "sha256:2725bd0a9925919b9b51739eea5f9e2bae91e83288108a9ad338b2e3a4435ee5"},
|
||||
{file = "PySocks-1.7.1.tar.gz", hash = "sha256:3f8804571ebe159c380ac6de37643bb4685970655d3bba243530d6558b799aa0"},
|
||||
]
|
||||
|
||||
[[package]]
|
||||
name = "python-dateutil"
|
||||
version = "2.9.0.post0"
|
||||
|
@ -3018,6 +3055,25 @@ dev = ["flake8", "flit", "mypy", "pandas-stubs", "pre-commit", "pytest", "pytest
|
|||
docs = ["ipykernel", "nbconvert", "numpydoc", "pydata_sphinx_theme (==0.10.0rc2)", "pyyaml", "sphinx (<6.0.0)", "sphinx-copybutton", "sphinx-design", "sphinx-issues"]
|
||||
stats = ["scipy (>=1.7)", "statsmodels (>=0.12)"]
|
||||
|
||||
[[package]]
|
||||
name = "selenium"
|
||||
version = "4.22.0"
|
||||
description = "Official Python bindings for Selenium WebDriver"
|
||||
optional = false
|
||||
python-versions = ">=3.8"
|
||||
files = [
|
||||
{file = "selenium-4.22.0-py3-none-any.whl", hash = "sha256:e424991196e9857e19bf04fe5c1c0a4aac076794ff5e74615b1124e729d93104"},
|
||||
{file = "selenium-4.22.0.tar.gz", hash = "sha256:903c8c9d61b3eea6fcc9809dc7d9377e04e2ac87709876542cc8f863e482c4ce"},
|
||||
]
|
||||
|
||||
[package.dependencies]
|
||||
certifi = ">=2021.10.8"
|
||||
trio = ">=0.17,<1.0"
|
||||
trio-websocket = ">=0.9,<1.0"
|
||||
typing_extensions = ">=4.9.0"
|
||||
urllib3 = {version = ">=1.26,<3", extras = ["socks"]}
|
||||
websocket-client = ">=1.8.0"
|
||||
|
||||
[[package]]
|
||||
name = "send2trash"
|
||||
version = "1.8.3"
|
||||
|
@ -3128,6 +3184,17 @@ files = [
|
|||
{file = "sniffio-1.3.1.tar.gz", hash = "sha256:f4324edc670a0f49750a81b895f35c3adb843cca46f0530f79fc1babb23789dc"},
|
||||
]
|
||||
|
||||
[[package]]
|
||||
name = "sortedcontainers"
|
||||
version = "2.4.0"
|
||||
description = "Sorted Containers -- Sorted List, Sorted Dict, Sorted Set"
|
||||
optional = false
|
||||
python-versions = "*"
|
||||
files = [
|
||||
{file = "sortedcontainers-2.4.0-py2.py3-none-any.whl", hash = "sha256:a163dcaede0f1c021485e957a39245190e74249897e2ae4b2aa38595db237ee0"},
|
||||
{file = "sortedcontainers-2.4.0.tar.gz", hash = "sha256:25caa5a06cc30b6b83d11423433f65d1f9d76c4c6a0c90e3379eaa43b9bfdb88"},
|
||||
]
|
||||
|
||||
[[package]]
|
||||
name = "soupsieve"
|
||||
version = "2.5"
|
||||
|
@ -3232,6 +3299,40 @@ files = [
|
|||
docs = ["myst-parser", "pydata-sphinx-theme", "sphinx"]
|
||||
test = ["argcomplete (>=3.0.3)", "mypy (>=1.7.0)", "pre-commit", "pytest (>=7.0,<8.2)", "pytest-mock", "pytest-mypy-testing"]
|
||||
|
||||
[[package]]
|
||||
name = "trio"
|
||||
version = "0.25.1"
|
||||
description = "A friendly Python library for async concurrency and I/O"
|
||||
optional = false
|
||||
python-versions = ">=3.8"
|
||||
files = [
|
||||
{file = "trio-0.25.1-py3-none-any.whl", hash = "sha256:e42617ba091e7b2e50c899052e83a3c403101841de925187f61e7b7eaebdf3fb"},
|
||||
{file = "trio-0.25.1.tar.gz", hash = "sha256:9f5314f014ea3af489e77b001861c535005c3858d38ec46b6b071ebfa339d7fb"},
|
||||
]
|
||||
|
||||
[package.dependencies]
|
||||
attrs = ">=23.2.0"
|
||||
cffi = {version = ">=1.14", markers = "os_name == \"nt\" and implementation_name != \"pypy\""}
|
||||
idna = "*"
|
||||
outcome = "*"
|
||||
sniffio = ">=1.3.0"
|
||||
sortedcontainers = "*"
|
||||
|
||||
[[package]]
|
||||
name = "trio-websocket"
|
||||
version = "0.11.1"
|
||||
description = "WebSocket library for Trio"
|
||||
optional = false
|
||||
python-versions = ">=3.7"
|
||||
files = [
|
||||
{file = "trio-websocket-0.11.1.tar.gz", hash = "sha256:18c11793647703c158b1f6e62de638acada927344d534e3c7628eedcb746839f"},
|
||||
{file = "trio_websocket-0.11.1-py3-none-any.whl", hash = "sha256:520d046b0d030cf970b8b2b2e00c4c2245b3807853ecd44214acd33d74581638"},
|
||||
]
|
||||
|
||||
[package.dependencies]
|
||||
trio = ">=0.11"
|
||||
wsproto = ">=0.14"
|
||||
|
||||
[[package]]
|
||||
name = "types-python-dateutil"
|
||||
version = "2.9.0.20240316"
|
||||
|
@ -3290,6 +3391,9 @@ files = [
|
|||
{file = "urllib3-2.2.1.tar.gz", hash = "sha256:d0570876c61ab9e520d776c38acbbb5b05a776d3f9ff98a5c8fd5162a444cf19"},
|
||||
]
|
||||
|
||||
[package.dependencies]
|
||||
pysocks = {version = ">=1.5.6,<1.5.7 || >1.5.7,<2.0", optional = true, markers = "extra == \"socks\""}
|
||||
|
||||
[package.extras]
|
||||
brotli = ["brotli (>=1.0.9)", "brotlicffi (>=0.8.0)"]
|
||||
h2 = ["h2 (>=4,<5)"]
|
||||
|
@ -3360,6 +3464,20 @@ files = [
|
|||
{file = "widgetsnbextension-4.0.11.tar.gz", hash = "sha256:8b22a8f1910bfd188e596fe7fc05dcbd87e810c8a4ba010bdb3da86637398474"},
|
||||
]
|
||||
|
||||
[[package]]
|
||||
name = "wsproto"
|
||||
version = "1.2.0"
|
||||
description = "WebSockets state-machine based protocol implementation"
|
||||
optional = false
|
||||
python-versions = ">=3.7.0"
|
||||
files = [
|
||||
{file = "wsproto-1.2.0-py3-none-any.whl", hash = "sha256:b9acddd652b585d75b20477888c56642fdade28bdfd3579aa24a4d2c037dd736"},
|
||||
{file = "wsproto-1.2.0.tar.gz", hash = "sha256:ad565f26ecb92588a3e43bc3d96164de84cd9902482b130d0ddbaa9664a85065"},
|
||||
]
|
||||
|
||||
[package.dependencies]
|
||||
h11 = ">=0.9.0,<1"
|
||||
|
||||
[[package]]
|
||||
name = "xyzservices"
|
||||
version = "2024.6.0"
|
||||
|
@ -3389,4 +3507,4 @@ test = ["big-O", "importlib-resources", "jaraco.functools", "jaraco.itertools",
|
|||
[metadata]
|
||||
lock-version = "2.0"
|
||||
python-versions = "^3.12"
|
||||
content-hash = "d070f377faf4ef9fdaf2f401e4af39ccf8d5989219c9b572a3e97d278d6cd438"
|
||||
content-hash = "40151747be7ccbaf8d89b9d49c90416afcd1483bfd8a62f14de0dbc693aa8df2"
|
||||
|
|
|
@ -15,6 +15,8 @@ pyarrow = "^16.1.0"
|
|||
great-tables = "^0.9.0"
|
||||
geopandas = "^0.14.4"
|
||||
folium = "^0.17.0"
|
||||
selenium = "^4.22.0"
|
||||
pillow = "^10.4.0"
|
||||
|
||||
|
||||
[tool.poetry.group.dev.dependencies]
|
||||
|
|
26
tools/fix-astro-img.py
Normal file
26
tools/fix-astro-img.py
Normal file
|
@ -0,0 +1,26 @@
|
|||
#!/usr/bin/env python3
|
||||
# Replaces all img tags with markdown ![image-tags](./aiming/at/output/dir)
|
||||
|
||||
import re
|
||||
import os
|
||||
import sys
|
||||
|
||||
if not os.getenv("QUARTO_PROJECT_RENDER_ALL"):
|
||||
sys.exit(0)
|
||||
|
||||
q_output_dir = os.getenv("QUARTO_PROJECT_OUTPUT_DIR")
|
||||
q_output_files = os.getenv("QUARTO_PROJECT_OUTPUT_FILES")
|
||||
if not q_output_files:
|
||||
sys.exit(1)
|
||||
|
||||
for fname in q_output_files.splitlines():
|
||||
if not fname.endswith(".md"):
|
||||
continue
|
||||
|
||||
with open(fname, "r") as f:
|
||||
content = f.read()
|
||||
|
||||
modified = re.sub(r'<img src="(.+?)".*/>', r"![fig](./\1)", content)
|
||||
|
||||
with open(fname, "w") as f:
|
||||
f.write(modified)
|
Loading…
Reference in a new issue