RQ4 — is energy trade a structurally different market? Same 35 economies, only HS chapter 27 (coal, crude, refined products, gas, electricity). Compare concentration, who leads, and where Austria sits against the all-merchandise network.
Why the node set is held fixed, and the caveats that matter for energy (2022 prices, suppliers outside the frame, pipeline-gas misattribution), are on the Data & method page.
Bridge. HS-2716 electrical energy is the customs view of the same physical flow that austria-energy-analysis measures hourly from ENTSO-E.
Shares and ranks are computed the same way for both networks, so every row is like-for-like. The Herfindahl index is the sum of squared export shares; its reciprocal is the number of equal-sized exporters that would be as concentrated as what we observe.
Show code
comparison = energy.comparison_table(G, E, focus_iso3=FOCUS, top_k=TOP_K, labels=LABELS)comparison.style.hide(axis="index")
The figure (RQ4 headline)
Left: how fast cumulative export share piles up as you walk down the exporter ranking — the steeper curve is the more concentrated market. Right: each economy’s share of energy exports against its share of merchandise exports. The dotted diagonal is “energy share = merchandise share”, so everything above it is an economy that punches above its trade weight in energy.
HS-27 is not one commodity. Splitting it by 4-digit heading shows what the network trades and what Austria — with no oil or gas of its own — buys and sells.
Who Austria buys HS-27 from and who it sells to, as a share of its own energy imports / exports inside this node set.
Show code
def _partner_shares(edges: pd.DataFrame, side: str, partner: str) -> pd.Series:"""Partner shares of Austria's energy trade on one side of the edge.""" own = edges.loc[edges[side] == FOCUS] totals = own.groupby(partner)["value_kusd"].sum().sort_values(ascending=False)return totals / totals.sum()suppliers = _partner_shares(energy_edges, "importer_iso3", "exporter_iso3")customers = _partner_shares(energy_edges, "exporter_iso3", "importer_iso3")top_partners = pd.DataFrame( {"rank": range(1, 7),"supplier": suppliers.index[:6],"share of AUT energy imports": suppliers.to_numpy()[:6],"customer": customers.index[:6],"share of AUT energy exports": customers.to_numpy()[:6], }).set_index("rank")top_partners.style.format( {"share of AUT energy imports": "{:.1%}", "share of AUT energy exports": "{:.1%}"})
What the node set does not see
Intra-set import shares for merchandise vs energy (the blind spot quantified). The interpretation is on Data & method.
Generated from the run above, so the numbers always match the tables and the figure.
Show code
total_conc = energy.concentration(total_shares, top_k=TOP_K)energy_conc = energy.concentration(energy_shares, top_k=TOP_K)total_pos = energy.country_position(total_shares, FOCUS)energy_pos = energy.country_position(energy_shares, FOCUS)n_nodes =int(energy_pos["n_economies"])total_value =float(all_edges["value_kusd"].sum())energy_value =float(energy_edges["value_kusd"].sum())top_energy =", ".join(energy_shares["iso3"].head(TOP_K))top_total =", ".join(total_shares["iso3"].head(TOP_K))def _share(mix_frame: pd.DataFrame, hs: str) ->float:"""Share of one HS heading in a composition table (0.0 if absent).""" row = mix_frame.loc[mix_frame["hs"] == hs, "share"]returnfloat(row.iloc[0]) iflen(row) else0.0def _value(mix_frame: pd.DataFrame, hs: str) ->float:"""Value (thousand USD) of one HS heading in a composition table.""" row = mix_frame.loc[mix_frame["hs"] == hs, "value_kusd"]returnfloat(row.iloc[0]) iflen(row) else0.0elec_network = _share(network_mix, "2716")elec_in, elec_out = _share(aut_imports_mix, "2716"), _share(aut_exports_mix, "2716")gas_out, refined_out = _share(aut_exports_mix, "2711"), _share(aut_exports_mix, "2710")electricity = flows.loc[flows["hs"] =="2716"]elec_customers =", ".join( electricity.loc[electricity["exporter_iso3"] == FOCUS] .groupby("importer_iso3")["value_kusd"] .sum() .sort_values(ascending=False) .index[:4])gas_in_bn = _value(aut_imports_mix, "2711") /1e6gas_out_bn = _value(aut_exports_mix, "2711") /1e6elec_in_bn = _value(aut_imports_mix, "2716") /1e6elec_out_bn = _value(aut_exports_mix, "2716") /1e6intra = sourcing.set_index(["scope", "importer"])["intra_share"]node_set =f"{n_nodes}-economy set"paragraphs = [f"**A different market.** Energy is {energy_value / total_value:.0%} of the value in this "f"network but a structurally different market. The top {TOP_K} exporters carry "f"**{energy_conc['top_k_share']:.0%} of energy exports** ({top_energy}) against "f"{total_conc['top_k_share']:.0%} for merchandise as a whole ({top_total}); the Herfindahl "f"index is {energy_conc['hhi']:.3f} vs {total_conc['hhi']:.3f}, i.e. "f"{energy_conc['effective_exporters']:.0f} effective suppliers instead of "f"{total_conc['effective_exporters']:.0f}. The leaders change too: the manufacturing hubs "f"that dominate merchandise trade cannot sell resources they do not have, so resource "f"exporters take their place — Russia and Norway sit far above the diagonal in the "f"right-hand panel, Germany and China well below it. Concentration, not connectivity, is "f"again the risk — the energy graph is nearly complete (density "f"{float(len(energy_edges)) / (n_nodes * (n_nodes -1)):.2f}), so everyone trades with "f"everyone; the volume just sits on very few sources.",f"**Caveat on the year.** {config.YEAR} is the energy-crisis year: BACI values are USD, so "f"the energy subnetwork's {energy_value / total_value:.0%} value share reflects prices as "f"much as volumes, and Russia still leads the {config.YEAR} table despite sanctions that "f"were only partly in force. Read the concentration ranking as a snapshot of a market under "f"stress, not a structural constant.",f"**Austria.** Austria is {int(energy_pos['export_rank'])}th of {n_nodes} by energy exports "f"({energy_pos['export_share']:.1%} of the network's energy) and "f"{int(energy_pos['import_rank'])}th by energy imports — versus "f"{int(total_pos['export_rank'])}th and {int(total_pos['import_rank'])}th for merchandise. "f"It is a net energy importer ({energy_pos['net_export_kusd'] /1e6:+,.1f} bn USD). The "f"ranking flatters it: Austria produces almost no fossil fuel, so its energy exports are "f"re-exports and transit — {elec_out:.0%} electricity, {gas_out:.0%} gas and "f"{refined_out:.0%} refined products passed on to neighbours. Its imports are equally "f"intermediated: {suppliers.iloc[0]:.0%} arrive from {suppliers.index[0]}, which is a "f"routing fact, not a production one.",f"**The frame misses the real suppliers.** Only {intra[(LABELS[1], node_set)]:.0%} of this "f"node set's energy imports come from inside it, against "f"{intra[(LABELS[0], node_set)]:.0%} for merchandise; for Austria it is "f"{intra[(LABELS[1], FOCUS)]:.0%} vs {intra[(LABELS[0], FOCUS)]:.0%}. Europe's crude and LNG "f"largely originate outside the {n_nodes} economies modelled here (the Gulf, Kazakhstan, "f"North and West Africa). The energy subgraph therefore describes how energy is "f"*redistributed* within Europe, not where it ultimately comes from — a full dependence "f"analysis needs the world node set.",f"**Pipeline gas is badly captured — do not read dependence off it.** Austria's recorded "f"HS-2711 (gas) imports inside this node set are only {gas_in_bn:,.1f} bn USD while its gas "f"exports are {gas_out_bn:,.1f} bn, which would make Austria a net gas exporter in "f"{config.YEAR}. It was not: it burned mostly Russian pipeline gas. Customs statistics "f"attribute a flow to the declared consignment partner, so gas crossing hubs and traded "f"contracts lands on the wrong edge. Treat merchandise data as evidence about *value "f"routed*, and use physical flow data for dependence.",f"**Bridge to [austria-energy-analysis](https://github.com/fatemeh-studio/"f"austria-energy-analysis).** Electricity (HS-2716) is {elec_network:.0%} of the energy "f"network's value but {elec_out:.0%} of Austria's energy exports and {elec_in:.0%} of its "f"energy imports — {elec_out_bn:,.1f} bn USD out against {elec_in_bn:,.1f} bn in, an almost "f"perfectly balanced position with its neighbours ({elec_customers}). That is the same "f"physical flow the sibling project measures hourly from ENTSO-E: there, Austria's renewable "f"share of electricity reaches 86% (2024), gas generation falls by a third, and each GW of "f"wind+solar cuts the day-ahead price by 14.3 EUR/MWh. Read together: this project locates "f"Austria in Europe's energy exchange (mid-sized, intermediating, import-dependent), the "f"other one explains what the exchange is for.",]display(Markdown("\n\n".join(paragraphs)))