Skip to content

Commit 4ced69d

Browse files
visualize_coverage and revisit Australia
1 parent 3314667 commit 4ced69d

2 files changed

Lines changed: 431 additions & 207 deletions

File tree

Lines changed: 270 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,270 @@
1+
import os
2+
import numpy as np
3+
import pandas as pd
4+
import geopandas as gpd
5+
import matplotlib.pyplot as plt
6+
7+
from cybench.config import REPO_DIR, PATH_POLYGONS_DIR
8+
9+
# -------------------------------------------------------------------
10+
# AAGIS mapping
11+
# -------------------------------------------------------------------
12+
AAGIS_TO_NAME = {
13+
111: "NSW Far West",
14+
121: "NSW North West Slopes and Plains",
15+
122: "NSW Central West",
16+
123: "NSW Riverina",
17+
131: "NSW Tablelands (Northern Central and Southern)",
18+
132: "NSW Coastal",
19+
221: "VIC Mallee",
20+
222: "VIC Wimmera",
21+
223: "VIC Central North",
22+
231: "VIC Southern and Eastern Victoria",
23+
311: "QLD Cape York and the Gulf",
24+
312: "QLD West and South West",
25+
313: "QLD Central North",
26+
314: "QLD Charleville - Longreach",
27+
321: "QLD Eastern Darling Downs",
28+
322: "QLD Western Downs and Central Highlands",
29+
331: "QLD Southern Coastal - Curtis to Moreton",
30+
332: "QLD Northern Coastal - Mackay to Cairns",
31+
411: "SA Northern Pastoral",
32+
421: "SA Eyre Peninsula",
33+
422: "SA Murray Lands and Yorke Peninsula",
34+
431: "SA South East",
35+
511: "WA The Kimberley",
36+
512: "WA Pilbara and Central Pastoral",
37+
521: "WA Central and Southern Wheat Belt",
38+
522: "WA Northern and Eastern Wheat Belt",
39+
531: "WA South West Coastal",
40+
631: "TAS Tasmania",
41+
711: "NT Alice Springs Districts",
42+
712: "NT Barkly Tablelands",
43+
713: "NT Victoria River District - Katherine",
44+
714: "NT Top End Darwin",
45+
799: "Other territories",
46+
}
47+
48+
49+
# -------------------------------------------------------------------
50+
# Helpers
51+
# -------------------------------------------------------------------
52+
def check_region_mapping(df: pd.DataFrame, mapping: dict):
53+
"""Check consistency between mapping and CSV region names."""
54+
csv_regions = set(df["ABARES region"].unique())
55+
map_regions = set(mapping.values())
56+
57+
missing_in_csv = map_regions - csv_regions
58+
if missing_in_csv:
59+
print("⚠️ Names in mapping but missing in CSV:")
60+
print("\n".join(missing_in_csv))
61+
else:
62+
print("✅ All names in mapping exist in CSV.")
63+
64+
missing_in_map = csv_regions - map_regions
65+
if missing_in_map:
66+
print("⚠️ Names in CSV but missing in mapping:")
67+
print("\n".join(missing_in_map))
68+
else:
69+
print("✅ All names in CSV exist in mapping.")
70+
71+
72+
def load_geometries():
73+
"""Load and preprocess Australian regions shapefile."""
74+
shapefile_path = os.path.join(PATH_POLYGONS_DIR, "AU", "AU.shp")
75+
gdf = gpd.read_file(shapefile_path)
76+
77+
# Calculate total area in hectares
78+
gdf = gdf.to_crs(epsg=3577) # GDA94 / Australian Albers
79+
gdf["region_total_area_ha"] = gdf.geometry.area / 1e4
80+
gdf = gdf.to_crs(epsg=4326) # back to lat/lon
81+
82+
# Extract numeric AAGIS code
83+
gdf["AAGIS"] = gdf["adm_id"].str.replace("AU-", "").astype(int)
84+
gdf["region_name"] = gdf["AAGIS"].map(AAGIS_TO_NAME)
85+
return gdf
86+
87+
88+
def preprocess_wheat_data(df: pd.DataFrame) -> pd.DataFrame:
89+
"""Filter, pivot, and compute wheat production stats."""
90+
wheat = df[df["Variable"].isin(["Wheat produced (t)", "Wheat area sown (ha)"])]
91+
wheat = wheat.drop(columns=["RSE"], errors="ignore")
92+
93+
wheat_pivot = wheat.pivot_table(
94+
index=["Year", "ABARES region"], columns="Variable", values="Value"
95+
).reset_index()
96+
97+
wheat_pivot = wheat_pivot.rename(
98+
columns={
99+
"Year": "harvest_year",
100+
"Wheat produced (t)": "production",
101+
"Wheat area sown (ha)": "planted_area",
102+
}
103+
)
104+
wheat_pivot["yield"] = wheat_pivot["production"] / wheat_pivot["planted_area"]
105+
return wheat_pivot
106+
107+
108+
def compute_median_fraction(
109+
merged_gdf: gpd.GeoDataFrame, years: range
110+
) -> gpd.GeoDataFrame:
111+
"""Compute median planted fraction per region across given years."""
112+
df_filtered = merged_gdf[merged_gdf["harvest_year"].isin(years)].copy()
113+
records = []
114+
for adm_id, group in df_filtered.groupby("adm_id"):
115+
records.append(
116+
{
117+
"adm_id": adm_id,
118+
"region_name": group.iloc[0]["region_name"],
119+
"geometry": group.iloc[0]["geometry"],
120+
"planted_fraction": group["planted_fraction"].median(),
121+
}
122+
)
123+
return gpd.GeoDataFrame(records, geometry="geometry", crs=merged_gdf.crs)
124+
125+
126+
def plot_median_fraction(
127+
median_gdf: gpd.GeoDataFrame, gdf: gpd.GeoDataFrame, threshold: float
128+
):
129+
"""Plot median planted fraction map with annotations."""
130+
fig, ax = plt.subplots(figsize=(12, 10))
131+
median_gdf.plot(
132+
column="planted_fraction",
133+
ax=ax,
134+
cmap="viridis",
135+
edgecolor="black",
136+
linewidth=0.5,
137+
legend=True,
138+
legend_kwds={"label": "Median planted fraction (2003–2023)"},
139+
)
140+
gdf.boundary.plot(ax=ax, color="black", linewidth=0.8)
141+
142+
for _, row in median_gdf.iterrows():
143+
centroid = row["geometry"].centroid
144+
color = "green" if row["planted_fraction"] >= threshold else "red"
145+
ax.text(
146+
centroid.x,
147+
centroid.y,
148+
f"{row['planted_fraction']*100:.3f}", # as %
149+
ha="center",
150+
va="center",
151+
fontsize=8,
152+
color=color,
153+
weight="bold",
154+
)
155+
156+
ax.set_title("Median Wheat Planted Fraction in Australia (2003–2023)", fontsize=16)
157+
ax.axis("off")
158+
plt.show()
159+
160+
161+
# -------------------------------------------------------------------
162+
# Main pipeline
163+
# -------------------------------------------------------------------
164+
def main():
165+
# Load shapefile + CSV
166+
gdf = load_geometries()
167+
csv_path = os.path.join(
168+
REPO_DIR,
169+
"data_preparation",
170+
"crop_statistics_AU",
171+
"fdp-regional-historical.csv",
172+
)
173+
df = pd.read_csv(csv_path)
174+
175+
# Check mapping consistency
176+
check_region_mapping(df, AAGIS_TO_NAME)
177+
178+
# Preprocess wheat stats
179+
wheat_pivot = preprocess_wheat_data(df)
180+
181+
# Merge geodata with wheat stats
182+
merged_gdf = gdf.merge(
183+
wheat_pivot, left_on="region_name", right_on="ABARES region", how="left"
184+
)
185+
merged_gdf["planted_fraction"] = (
186+
merged_gdf["planted_area"] / merged_gdf["region_total_area_ha"]
187+
)
188+
189+
# Compute median per region
190+
years_to_include = range(2003, 2024)
191+
median_gdf = compute_median_fraction(merged_gdf, years_to_include)
192+
193+
# Plot
194+
threshold = 0.000005
195+
plot_median_fraction(median_gdf, gdf, threshold)
196+
197+
# Build cleaned wheat dataframe
198+
wheat_df = merged_gdf[
199+
["harvest_year", "production", "planted_area", "adm_id", "planted_fraction"]
200+
].copy()
201+
202+
wheat_df["crop_name"] = "wheat"
203+
wheat_df["country_code"] = "AU"
204+
wheat_df["yield"] = wheat_df["production"] / wheat_df["planted_area"]
205+
206+
wheat_df = wheat_df[
207+
[
208+
"crop_name",
209+
"country_code",
210+
"adm_id",
211+
"harvest_year",
212+
"yield",
213+
"planted_area",
214+
"production",
215+
"planted_fraction",
216+
]
217+
]
218+
219+
# Clean NaNs/infs
220+
wheat_df = wheat_df.replace([np.inf, -np.inf], np.nan).dropna()
221+
222+
# Add median stats
223+
median_stats = (
224+
wheat_df.groupby("adm_id")
225+
.agg(
226+
median_planted_area=("planted_area", "median"),
227+
median_planted_fraction=("planted_fraction", "median"),
228+
)
229+
.reset_index()
230+
)
231+
wheat_df = wheat_df.merge(median_stats, on="adm_id", how="left")
232+
233+
# Filter invalid rows
234+
wheat_df = wheat_df[
235+
~(
236+
(wheat_df["production"] == 0)
237+
& (wheat_df["planted_area"] == 0)
238+
& (wheat_df["yield"] == 0)
239+
)
240+
& (wheat_df["median_planted_fraction"] >= threshold)
241+
]
242+
243+
# Final cleanup
244+
wheat_df["harvest_year"] = wheat_df["harvest_year"].astype(int)
245+
wheat_df[["yield", "planted_area", "production"]] = wheat_df[
246+
["yield", "planted_area", "production"]
247+
].round(3)
248+
249+
wheat_df = wheat_df[
250+
[
251+
"crop_name",
252+
"country_code",
253+
"adm_id",
254+
"harvest_year",
255+
"yield",
256+
"planted_area",
257+
"production",
258+
]
259+
]
260+
# Save
261+
out_path = os.path.join(
262+
REPO_DIR, "cybench", "data", "wheat", "AU", "yield_wheat_AU.csv"
263+
)
264+
wheat_df.to_csv(out_path, index=False)
265+
print(f"✅ Saved cleaned dataset: {out_path}")
266+
print("Final shape:", wheat_df.shape)
267+
268+
269+
if __name__ == "__main__":
270+
main()

0 commit comments

Comments
 (0)