Update article and rename to index.qmd
This commit is contained in:
parent
fed35fcfd2
commit
7d1d929b0e
1 changed files with 145 additions and 76 deletions
|
@ -1,7 +1,5 @@
|
||||||
---
|
---
|
||||||
title: Nuclear Explosions
|
title: Nuclear Explosions
|
||||||
author: Marty Oehme
|
|
||||||
output-dir: out
|
|
||||||
references:
|
references:
|
||||||
- type: techreport
|
- type: techreport
|
||||||
id: Bergkvist2000
|
id: Bergkvist2000
|
||||||
|
@ -18,16 +16,6 @@ references:
|
||||||
title: "Nuclear Explosions 1945 - 1998"
|
title: "Nuclear Explosions 1945 - 1998"
|
||||||
page: 1-42
|
page: 1-42
|
||||||
issn: 1104-9154
|
issn: 1104-9154
|
||||||
format:
|
|
||||||
html:
|
|
||||||
toc: true
|
|
||||||
code-fold: true
|
|
||||||
typst:
|
|
||||||
toc: true
|
|
||||||
echo: false
|
|
||||||
docx:
|
|
||||||
toc: true
|
|
||||||
echo: false
|
|
||||||
---
|
---
|
||||||
|
|
||||||
```{python}
|
```{python}
|
||||||
|
@ -43,7 +31,21 @@ from matplotlib import pyplot as plt
|
||||||
|
|
||||||
sns.set_theme(style="darkgrid")
|
sns.set_theme(style="darkgrid")
|
||||||
sns.set_context("notebook")
|
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
|
||||||
|
# | echo: false
|
||||||
schema_overrides = (
|
schema_overrides = (
|
||||||
{
|
{
|
||||||
col: pl.Categorical
|
col: pl.Categorical
|
||||||
|
@ -53,13 +55,31 @@ schema_overrides = (
|
||||||
| {col: pl.String for col in ["year", "name"]}
|
| {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 = (
|
df = (
|
||||||
pl.read_csv(
|
pl.read_csv(
|
||||||
"data/nuclear_explosions.csv",
|
"data/nuclear_explosions.csv",
|
||||||
schema_overrides=schema_overrides,
|
schema_overrides=schema_overrides,
|
||||||
null_values=["NA"],
|
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))
|
.with_columns(year=pl.col("date").dt.year().cast(pl.Int32))
|
||||||
)
|
)
|
||||||
```
|
```
|
||||||
|
@ -69,7 +89,7 @@ df = (
|
||||||
The following is a re-creation and expansion of some of the graphs found in the
|
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
|
@Bergkvist2000 produced report on nuclear explosions between 1945 and 1998. It
|
||||||
is primarily a reproduction of key plots from the original report.
|
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
|
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
|
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
|
library polars-rs for data loading and transformation. All the code used to
|
||||||
|
@ -77,9 +97,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
|
text document, and separately as well. PDF and Docx formats are available with
|
||||||
the plotting results only.
|
the plotting results only.
|
||||||
|
|
||||||
Their original purpose was the collection of a long list of all the nuclear
|
The authors' original purpose was the collection of a long list of all the
|
||||||
explosions occurring between those years, as well as analysing the responsible
|
nuclear explosions occurring between those years, as well as analysing the
|
||||||
nations, tracking the types and purposes of the explosions, as well as
|
responsible nations, tracking the types and purposes of the explosions and
|
||||||
connecting the rise and fall of nuclear explosion numbers to historical events
|
connecting the rise and fall of nuclear explosion numbers to historical events
|
||||||
throughout.
|
throughout.
|
||||||
|
|
||||||
|
@ -90,13 +110,13 @@ throughout.
|
||||||
## Nuclear devices
|
## Nuclear devices
|
||||||
|
|
||||||
There are two main kinds of nuclear device: those based entirely, on fission,
|
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
|
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
|
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
|
fusion explosion can be practically unlimited. The explosive power of a
|
||||||
nuclear explosion is expressed in ktlotons, (kt) or megatons (Mt), which
|
nuclear explosion is expressed in kilotons, (kt) or megatons (Mt), which
|
||||||
correspond to 1000 and i million'tonnes, of conventional explosive (TNT),
|
correspond to 1000 and 1 million tonnes, of conventional explosive (TNT),
|
||||||
respectively.
|
respectively.
|
||||||
|
|
||||||
[@Bergkvist2000, 6]
|
[@Bergkvist2000, 6]
|
||||||
|
@ -108,8 +128,9 @@ each country had explode, seen in @tbl-yields.
|
||||||
```{python}
|
```{python}
|
||||||
# | label: tbl-yields
|
# | label: tbl-yields
|
||||||
# | tbl-cap: "Total number and yields of explosions"
|
# | tbl-cap: "Total number and yields of explosions"
|
||||||
|
# | output: asis
|
||||||
|
|
||||||
from great_tables import GT, md
|
from great_tables import GT
|
||||||
|
|
||||||
df_yields = (
|
df_yields = (
|
||||||
df.select(["country", "id_no", "yield_lower", "yield_upper"])
|
df.select(["country", "id_no", "yield_lower", "yield_upper"])
|
||||||
|
@ -119,11 +140,17 @@ df_yields = (
|
||||||
pl.col("id_no").len().alias("count"),
|
pl.col("id_no").len().alias("count"),
|
||||||
pl.col("yield_avg").sum(),
|
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)
|
.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)
|
GT(df_yields)
|
||||||
.tab_source_note(
|
.tab_source_note(
|
||||||
source_note="Source: Author's elaboration based on Bergkvist and Ferm (2000)."
|
source_note="Source: Author's elaboration based on Bergkvist and Ferm (2000)."
|
||||||
|
@ -131,22 +158,33 @@ df_yields = (
|
||||||
.tab_spanner(label="Totals", columns=["count", "yield_avg"])
|
.tab_spanner(label="Totals", columns=["count", "yield_avg"])
|
||||||
.tab_stub(rowname_col="country")
|
.tab_stub(rowname_col="country")
|
||||||
.tab_stubhead(label="Country")
|
.tab_stubhead(label="Country")
|
||||||
.cols_label(
|
.cols_label(count="Count", yield_avg="Yield in kt", yield_per_ex="Yield average")
|
||||||
count="Count",
|
|
||||||
yield_avg="Yield in kt",
|
|
||||||
)
|
|
||||||
.fmt_integer(columns="count")
|
.fmt_integer(columns="count")
|
||||||
.fmt_number(columns="yield_avg", decimals=1)
|
.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
|
## Numbers over time
|
||||||
|
|
||||||
When investigating the nuclear explosions in the world, let us first start by
|
In the examination of global nuclear detonations, our initial focus shall be
|
||||||
looking at how many explosions occurred each year in total. This hides the
|
quantifying the annual incidence of the events in aggregate. While it obscures
|
||||||
specific details of who was responsible and which types were involved but
|
the specific details of the responsible nations and which diversity of types
|
||||||
instead paints a much stronger picture of the overall dimension of nuclear
|
tested, it instead paints a much stronger picture of the overall abstracted
|
||||||
testing, as can be seen in @fig-total.
|
dimension of nuclear testing throughout history, as depicted in @fig-total.
|
||||||
|
|
||||||
```{python}
|
```{python}
|
||||||
# | label: fig-total
|
# | label: fig-total
|
||||||
|
@ -171,17 +209,29 @@ with sns.axes_style(
|
||||||
del per_year
|
del per_year
|
||||||
```
|
```
|
||||||
|
|
||||||
As we can see, the numbers of explosions rise increasingly towards 1957 and
|
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 reasons for
|
sharply until 1958, before dropping off for a year in 1959. The reason for this
|
||||||
this drop are not entirely clear, but it is very likely that the data are
|
drop should primarily be found in the start of the 'Treaty of Test Ban' which
|
||||||
simply missing for these years.
|
put limits and restraints on the testing of above-ground nuclear armaments, as
|
||||||
<!-- FIXME: The reasons for this are a non-proliferation pact, in article -->
|
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 -->
|
<!-- TODO: Extract exact numbers from data on-the-fly -->
|
||||||
There is another, very steep, rise in 1962 with over 175 recorded explosions,
|
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.
|
before an even sharper drop-off the following year down to just 50 explosions.
|
||||||
|
Afterward the changes appear less sharp and the changes remain between 77 and
|
||||||
Afterwards the changes appear less sharp and the changes remain between 77 and
|
|
||||||
24 explosions per year, with a slight downward tendency.
|
24 explosions per year, with a slight downward tendency.
|
||||||
|
|
||||||
While these numbers show the overall proliferation of nuclear power, let us now
|
While these numbers show the overall proliferation of nuclear power, let us now
|
||||||
|
@ -191,6 +241,7 @@ of explosions over time by country can be seen in @fig-percountry.
|
||||||
```{python}
|
```{python}
|
||||||
# | label: fig-percountry
|
# | label: fig-percountry
|
||||||
# | fig-cap: "Nuclear explosions by country, 1945-98"
|
# | fig-cap: "Nuclear explosions by country, 1945-98"
|
||||||
|
|
||||||
keys = df.select("date").unique().join(df.select("country").unique(), how="cross")
|
keys = df.select("date").unique().join(df.select("country").unique(), how="cross")
|
||||||
per_country = keys.join(
|
per_country = keys.join(
|
||||||
df.group_by(["date", "country"], maintain_order=True).len(),
|
df.group_by(["date", "country"], maintain_order=True).len(),
|
||||||
|
@ -199,7 +250,7 @@ per_country = keys.join(
|
||||||
coalesce=True,
|
coalesce=True,
|
||||||
).with_columns(pl.col("len").fill_null(0))
|
).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_xlabel("Year")
|
||||||
g.set_ylabel("Count")
|
g.set_ylabel("Count")
|
||||||
plt.setp(
|
plt.setp(
|
||||||
|
@ -211,14 +262,14 @@ del per_country
|
||||||
|
|
||||||
Once again we can see the visibly steep ramp-up to 1962, though it becomes
|
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
|
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
|
of the countries, with only France catching up to the US numbers and China
|
||||||
ultimately overtaking them in the 1990s.
|
ultimately overtaking them in the 1990s.
|
||||||
|
|
||||||
However, here it also becomes more clear how the UK was responsible for some
|
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
|
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
|
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
|
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
|
on the number of explosions that have occurred underground and above-ground
|
||||||
|
@ -273,6 +324,7 @@ with sns.axes_style("darkgrid", {"xtick.bottom": True, "ytick.left": True}):
|
||||||
hue="country",
|
hue="country",
|
||||||
multiple="stack",
|
multiple="stack",
|
||||||
binwidth=365,
|
binwidth=365,
|
||||||
|
palette=country_colors,
|
||||||
)
|
)
|
||||||
|
|
||||||
g.xaxis.set_major_locator(mdates.YearLocator(base=5))
|
g.xaxis.set_major_locator(mdates.YearLocator(base=5))
|
||||||
|
@ -293,25 +345,19 @@ shift from above-ground to underground tests, starting with the year 1962.
|
||||||
|
|
||||||
## Locations
|
## 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.
|
||||||
|
|
||||||
```{python}
|
```{python}
|
||||||
# | label: fig-worldmap
|
# | label: worldmap-setup
|
||||||
# | fig-cap: "World map of nuclear explosions, 1945-98"
|
# | output: false
|
||||||
import folium
|
import folium
|
||||||
import geopandas as gpd
|
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")
|
df_pd = df.with_columns().to_pandas().set_index("date")
|
||||||
gdf = gpd.GeoDataFrame(
|
gdf = gpd.GeoDataFrame(
|
||||||
df_pd,
|
df_pd,
|
||||||
|
@ -320,25 +366,18 @@ gdf = gpd.GeoDataFrame(
|
||||||
)
|
)
|
||||||
del df_pd
|
del df_pd
|
||||||
|
|
||||||
country_colors = {
|
def rgb_to_hex(rgb: tuple[float,float,float]) -> str:
|
||||||
"USA": "darkblue",
|
return "#" + "".join([format(int(c*255), '02x') for c in rgb])
|
||||||
"USSR": "darkred",
|
|
||||||
"FRANCE": "pink",
|
|
||||||
"UK": "black",
|
|
||||||
"CHINA": "purple",
|
|
||||||
"INDIA": "orange",
|
|
||||||
"PAKIST": "green",
|
|
||||||
}
|
|
||||||
|
|
||||||
m = folium.Map(tiles="cartodb positron")
|
m = folium.Map(tiles="cartodb positron")
|
||||||
for country in country_colors.keys():
|
for country in country_colors.keys():
|
||||||
fg = folium.FeatureGroup(name=country, show=True).add_to(m)
|
fg = folium.FeatureGroup(name=country, show=True).add_to(m)
|
||||||
folium.GeoJson(
|
folium.GeoJson(
|
||||||
gdf[gdf["country"].str.contains(country)],
|
gdf[gdf["country"] == country],
|
||||||
name="Nuclear Explosions",
|
name="Nuclear Explosions",
|
||||||
marker=folium.Circle(radius=3, fill_opacity=0.4),
|
marker=folium.Circle(radius=3, fill_opacity=0.4),
|
||||||
style_function=lambda x: {
|
style_function=lambda x: {
|
||||||
"color": country_colors[x["properties"]["country"]],
|
"color": rgb_to_hex(country_colors[x["properties"]["country"]]),
|
||||||
"radius": (
|
"radius": (
|
||||||
x["properties"]["magnitude_body"]
|
x["properties"]["magnitude_body"]
|
||||||
if x["properties"]["magnitude_body"] > 0
|
if x["properties"]["magnitude_body"] > 0
|
||||||
|
@ -368,15 +407,45 @@ for country in country_colors.keys():
|
||||||
),
|
),
|
||||||
).add_to(fg)
|
).add_to(fg)
|
||||||
folium.LayerControl().add_to(m)
|
folium.LayerControl().add_to(m)
|
||||||
|
```
|
||||||
|
|
||||||
|
::: {#fig-worldmap}
|
||||||
|
|
||||||
|
:::: {.content-visible when-format="html"}
|
||||||
|
|
||||||
|
```{python}
|
||||||
|
# | label: worldmap-html
|
||||||
m
|
m
|
||||||
```
|
```
|
||||||
|
|
||||||
That is all for now.
|
::::
|
||||||
There are undoubtedly more explorations to undertake,
|
|
||||||
but this is it for the time being.
|
|
||||||
|
|
||||||
<!-- Ideas TODO:
|
:::: {.content-visible unless-format="html" width=80%}
|
||||||
- do not just use 'count' of explosions but yields
|
|
||||||
- compare number to yields for ctrys
|
```{python}
|
||||||
- count up total number per country in table
|
# | label: worldmap-non-html
|
||||||
-->
|
# ENSURE SELENIUM IS INSTALLED
|
||||||
|
m.png_enabled = True
|
||||||
|
m
|
||||||
|
```
|
||||||
|
|
||||||
|
::::
|
||||||
|
|
||||||
|
World map of nuclear explosions, 1945-98
|
||||||
|
:::
|
||||||
|
|
||||||
|
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}
|
||||||
|
:::
|
Loading…
Reference in a new issue