Drivers of Liquidity Strength from Retail Station Performance: An Exploratory and Inferential Analysis in a Downstream Oil & Gas Treasury Function
Author
Juliet Okechukwu
Published
May 10, 2026
1. Executive Summary
This study examines how retail station performance translates into daily liquidity strength within the treasury function of a downstream oil and gas company. The business problem addressed is the difficulty of forecasting reliable cash inflows to fund depot loadings, supplier payments, salaries, and statutory obligations — all of which depend on revenue generated by the retail network.
A primary dataset was extracted from the company’s retail reporting system and Business Central ERP covering 193 stations over October–December 2025 (579 station-month observations). Variables include PMS, AGO, and DPK volumes, realised prices, product mix, and computed revenues. Exploratory analysis revealed severe right-skewness in revenue distribution (skewness = 4.36) and a Herfindahl-Hirschman Index (HHI) of 80.6, confirming structural liquidity concentration risk: the top two stations alone generate ₦90.6 million per day. Hypothesis testing confirmed statistically significant AGO volume growth across Q4 — driven by Nigeria’s power supply deficits, industrial activity cycles, and logistics demand — validating the existing inventory pre-positioning policy. Correlation analysis showed revenue is almost entirely volume-driven (r = 0.9995), not price-driven. A log-linear regression model identified PMS volume as the strongest predictor of station revenue (R² = 0.82), and a named residual leaderboard exposed stations generating significantly more or less than their operational profile predicts.
The study recommends that treasury build inflow forecasts on PMS throughput assumptions rather than price assumptions, designate Asaba Summit Junction and Ibafo as liquidity-critical assets subject to daily monitoring and priority supply allocation, and prioritise PMS storage capacity in the 2026 capital expenditure plan as the single highest-return intervention for strengthening the company’s funding position.
Organisation: Rainoil Limited. A downstream oil and gas company operating a retail fuel distribution network across Nigeria, encompassing petroleum product procurement from the Dangote Refinery and other domestic suppliers, depot operations, and a network of 193 retail stations dispensing PMS (petrol), AGO (diesel), and DPK (kerosene).
My role in treasury is to ensure that sufficient cash is available daily to fund depot loadings, supplier settlements, salaries, taxes, and statutory obligations. These decisions are executed at the bank position level but are fundamentally driven by the revenue-generating capacity of the retail station network. Understanding how stations perform operationally is therefore essential for anticipating liquidity strength, planning funding requirements, and setting appropriate liquidity buffers.
Technique Justification — why each method is directly relevant to my role:
1. Exploratory Data Analysis: My treasury role begins every morning with an assessment of where cash is, where it needs to be, and whether the day’s inflows are sufficient to cover scheduled outflows. EDA formalises this instinct into a reproducible diagnostic routine. In this analysis, EDA on the Q4 2025 station sales data immediately surfaced two material quality issues — five stations with zero-volume months (ambiguous between genuine closure and reporting failure) — and a severe right-skew in revenue distribution (skewness = 4.36) driven by two mega-stations whose combined Q4 revenue of approximately ₦8.25 billion represented a disproportionate share of the ₦122.6 billion network total. In treasury terms, this concentration risk is directly relevant: if two stations account for that share of inflows, any operational disruption at either site creates an immediate and material liquidity gap the rest of the network cannot absorb in the short term.
2. Data Visualisation: Treasury reporting is not done for analysts — it is done for GMDs, CFOs, and board treasury committees who need to make funding decisions in minutes, not hours. In this analysis, five coordinated plots told a single operational story: the efficiency frontier showed which stations extract the most revenue per litre of product sold; the regional momentum chart showed which geographies are accelerating versus contracting as liquidity contributors; the concentration chart made the HHI risk visceral; the AGO growth distribution validated the seasonal pre-positioning policy; and the regression residual leaderboard named the specific stations whose performance warrants immediate management action. These visuals are precisely what I would embed in a weekly treasury dashboard.
3. Hypothesis Testing: Treasury decisions are frequently justified by assumptions that have never been formally tested. A standing assumption in downstream operations is that AGO demand is seasonal — driven by chronic power supply deficits, industrial and commercial activity cycles, long-haul logistics demand, and agricultural mechanisation — justifying pre-positioning of AGO inventory and the associated funding commitment of 60–90 days of working capital. In this analysis, a formal one-sample t-test evaluated exactly this assumption. The discipline of stating H₀ and H₁, checking normality, reporting effect size alongside the p-value, and confirming with a non-parametric backup is the same discipline required when presenting a funding recommendation to the CFO — the conclusion must be defensible, not anecdotal.
4. Correlation Analysis: In treasury, the question is never simply “what happened?” but “what is driving it, and can I see it coming?” The correlation matrix identified that total volume and total revenue are nearly perfectly collinear (r ≈ 0.9995), confirming that revenue forecasting is a throughput problem. To make this concrete: PMS price variation across the network is less than ₦70 per litre, while PMS volume varies by a factor of more than 25×. Price scenarios contribute almost nothing to forecast accuracy; supply allocation decisions are everything. This finding directly informs how I structure the treasury cash flow model — it should take its primary input from Dangote Refinery allocation data, not price assumptions.
5. Linear Regression: The most consequential treasury decision made daily is whether to draw on a credit facility — and if so, how much and for how long. A regression model translates each station’s operational characteristics into a predicted revenue contribution, and the residuals identify which stations are systematically over- or under-performing relative to what their operational profile would predict. Stations with large negative residuals are not just underperforming — they are structural funding drains. The coefficient on PMS volume directly justifies prioritising PMS storage capacity in the 2026 capex plan, because storage constraints are the binding supply-side ceiling on inflows.
3. Data Collection, Provenance & Sampling
Source: Retail sales records extracted from Rainoil Limited’s Business Central ERP and retail reporting system by the Finance Manager (author). Data reflects pump-level sales transactions aggregated monthly per station by the company’s retail operations team.
Collection Method: Direct extraction from the company’s internal ERP system. The Finance Manager downloaded monthly sales summaries for all active stations, covering the period October to December 2025. The data was exported as an Excel workbook with one row per station and separate column groups for each month, covering volumes, prices, and computed revenues for PMS, AGO, and DPK.
Sampling Frame & Justification: This is a census — all 193 retail stations in the Rainoil network are included. No sampling design was required because the entire population was available and the dataset (579 station-month observations) was fully manageable computationally. A census is statistically preferable to a sample here because (a) the network is finite and known, (b) the treasury question concerns specific high-contributing stations rather than population-level inference, and (c) omitting any station would introduce coverage bias into the concentration and regression analyses. Three months provides sufficient cross-sectional variation (n=579) for EDA, hypothesis testing, correlation, and regression, which rely more on cross-sectional variation than long time-series depth.
Time Period: October 1 to December 31, 2025 (Q4 2025). This covers 91 calendar days across three calendar months. The three-month window captures within-quarter seasonal variation and supports month-on-month growth analysis. A longer panel would improve time-series and seasonality modelling but was not available for this submission.
Ethical Approvals & Data-Sharing Restrictions: Formal permission was obtained from management to use this operational dataset strictly for academic purposes in this examination. The analysis does not disclose commercially sensitive pricing, margin, or procurement information beyond what is required for statistical interpretation. No customer-level or personally identifiable data is included. Station names are used for analytical purposes and are not disclosed outside this academic exercise. Findings will also be presented to management as part of ongoing efforts to strengthen treasury forecasting. Due to the confidential nature of the underlying data, the raw dataset is available on request from the author subject to Rainoil management approval.
Variables:
Variable
Type
Description
station
Categorical
Station name (193 unique values)
month
Categorical / Date
Oct, Nov, Dec 2025
pms_vol
Numeric
PMS (petrol) volume sold (litres)
ago_vol
Numeric
AGO (diesel) volume sold (litres)
dpk_vol
Numeric
DPK (kerosene) volume sold (litres)
avg_pms_price
Numeric
Average realised PMS price (₦/litre)
avg_ago_price
Numeric
Average realised AGO price (₦/litre)
avg_dpk_price
Numeric
Average realised DPK price (₦/litre)
pms_rev
Numeric
PMS revenue for the month (₦)
ago_rev
Numeric
AGO revenue for the month (₦)
dpk_rev
Numeric
DPK revenue for the month (₦)
total_vol
Numeric
Total volume across all products (litres)
total_rev
Numeric
Total station revenue for the month (₦)
pms_share
Numeric
PMS volume as proportion of total volume (0–1)
4. Data Description
4.1 Load Libraries and Data
Code
library(readxl)library(tidyverse)
── Attaching core tidyverse packages ──────────────────────── tidyverse 2.0.0 ──
✔ dplyr 1.2.1 ✔ readr 2.2.0
✔ forcats 1.0.1 ✔ stringr 1.6.0
✔ ggplot2 4.0.3 ✔ tibble 3.3.1
✔ lubridate 1.9.5 ✔ tidyr 1.3.2
✔ purrr 1.2.2
── Conflicts ────────────────────────────────────────── tidyverse_conflicts() ──
✖ dplyr::filter() masks stats::filter()
✖ dplyr::lag() masks stats::lag()
ℹ Use the conflicted package (<http://conflicted.r-lib.org/>) to force all conflicts to become errors
Code
data <-read_excel("2025_OCT_NOV_DEC_SALES_DATA.xlsx")
Warning: There was 1 warning in `summarise()`.
ℹ In argument: `across(...)`.
Caused by warning:
! The `...` argument of `across()` is deprecated as of dplyr 1.1.0.
Supply arguments directly to `.fns` through an anonymous function instead.
# Previously
across(a:b, mean, na.rm = TRUE)
# Now
across(a:b, \(x) mean(x, na.rm = TRUE))
# A tibble: 1 × 0
5. Exploratory Data Analysis
Theory Recap
Exploratory Data Analysis (EDA) is the systematic examination of a dataset before formal modelling. It encompasses summary statistics, missing value analysis, outlier detection, and distributional assessment (Adi, 2026, Ch. 4). The objective is not to confirm hypotheses but to surface unexpected structure — skewness, outliers, data quality issues — that would otherwise invalidate downstream analyses. Anscombe’s Quartet (1973) famously demonstrated that four datasets with identical summary statistics can have radically different distributions; EDA guards against this trap.
Business Justification
In treasury, EDA is the analytical equivalent of the morning bank position review — a diagnostic sweep before commitments are made. A revenue distribution that is more concentrated than it appears in averages implies a fundamentally different liquidity risk profile than a uniform one. EDA is the only technique that surfaces this before it becomes a crisis.
Data Quality Issue 1 — Zero-Volume Station-Month Observations
Station
Month
PMS Vol (L)
AGO Vol (L)
DPK Vol (L)
Revenue (₦)
Rainoil Ilorin - Sobi Road
Oct
0
0
0
0
Rainoil Ilorin - Sobi Road
Nov
0
0
0
0
Rainoil Ilorin - Sobi Road
Dec
0
0
0
0
Rainoil Portharcourt - Igwuruta
Oct
0
0
0
0
Rainoil Portharcourt - Igwuruta
Nov
0
0
0
0
Rainoil Ughelli Post Office
Oct
0
0
0
0
Rainoil Ughelli Post Office
Nov
0
0
0
0
Rainoil Uselu Shell
Oct
0
0
0
0
Handling: Zero-volume station-months are retained in network totals and concentration metrics but excluded from price-based and regression calculations to prevent division-by-zero and leverage distortion. Rainoil Ilorin – Sobi Road (zero across all three months) is flagged as a full-quarter closure pending operational verification.
Code
tibble(Metric =c("Network HHI (×10,000 scale)","Top 2 stations — Q4 revenue share","Top 5 stations — Q4 revenue share","Top 10 stations — Q4 revenue share","Daily revenue at risk (top-2 disruption)","Revenue skewness (station level)","Mean station Q4 revenue","Median station Q4 revenue" ),Value =c(formatC(hhi, format="f", digits=1), scales::percent(top2_share, accuracy=0.01), scales::percent(st_summary |>slice_max(total_rev_q4,n=5) |>summarise(s=sum(rev_share)) |>pull(s), accuracy=0.01), scales::percent(st_summary |>slice_max(total_rev_q4,n=10) |>summarise(s=sum(rev_share)) |>pull(s), accuracy=0.01),paste0("₦", formatC(top2_daily/1e6, format="f", digits=1), "M/day"),round(skewness(st_summary$total_rev_q4), 2),paste0("₦", formatC(mean(st_summary$total_rev_q4)/1e6, format="f", digits=1), "M"),paste0("₦", formatC(median(st_summary$total_rev_q4)/1e6, format="f", digits=1), "M") ),`Treasury Interpretation`=c("Unconcentrated by competition-law standards — but tail risk for treasury is material","A single stock-out at either site = ₦90.6M/day shortfall the network cannot replace","5 stations drive 1 in 10 naira of all network cash inflow","Monitoring 10 stations gives oversight of 16% of total inflows — high-leverage target","Minimum contingency buffer treasury must hold against simultaneous top-2 outage","Severe right-tail — mean-based forecasting systematically understates tail exposure","Mean >> Median confirms right-skew; average is not representative of typical station","Typical station generates ₦634M in Q4 — far below the network mean of ₦981M" )) |>kable() |>kable_styling(bootstrap_options =c("striped","hover"), full_width =TRUE) |>column_spec(3, italic =TRUE) |>row_spec(c(2,5), background ="#fff3cd") |>row_spec(0, bold =TRUE)
Data Quality Issue 2 — Revenue Concentration (HHI Analysis)
Metric
Value
Treasury Interpretation
Network HHI (×10,000 scale)
80.6
Unconcentrated by competition-law standards — but tail risk for treasury is material
Top 2 stations — Q4 revenue share
6.73%
A single stock-out at either site = ₦90.6M/day shortfall the network cannot replace
Top 5 stations — Q4 revenue share
10.73%
5 stations drive 1 in 10 naira of all network cash inflow
Top 10 stations — Q4 revenue share
16.37%
Monitoring 10 stations gives oversight of 16% of total inflows — high-leverage target
Daily revenue at risk (top-2 disruption)
₦90.6M/day
Minimum contingency buffer treasury must hold against simultaneous top-2 outage
Revenue skewness (station level)
4.37
Severe right-tail — mean-based forecasting systematically understates tail exposure
Mean station Q4 revenue
₦638.5M
Mean >> Median confirms right-skew; average is not representative of typical station
Median station Q4 revenue
₦572.7M
Typical station generates ₦634M in Q4 — far below the network mean of ₦981M
Guiding Question — What does the distribution of total revenue tell us about the business process that generated it?
The severe right-skew (skewness = 4.36) and the mean-median gap (₦981M vs ₦634M at station level) are not a statistical curiosity — they are a direct signature of how the Nigerian downstream fuel retail market is structured. Station revenue is the product of volume and price. Price barely varies (less than ₦70/litre across the network). So revenue differences are almost entirely volume differences, and volume is determined by location, catchment size, and road traffic flow — factors that follow a power-law distribution in any urban hierarchy. Asaba Summit Junction and Ibafo are not merely lucky stations; they sit at the intersection of major highway corridors and dense commercial activity. The treasury implication is that the network’s cash inflow distribution will always be right-skewed as long as the station footprint mirrors Nigeria’s uneven population and commerce distribution. Concentration risk is structural, not temporary.
Code
import pandas as pdimport numpy as npfrom scipy import statsimport matplotlib.pyplot as pltimport matplotlib.ticker as mtickerimport matplotlib.patches as mpatchesimport seaborn as snsimport warningswarnings.filterwarnings('ignore')# ── Region classifier ─────────────────────────────────────────────────────────def classify_region(name): n =str(name).lower()ifany(x in n for x in ['fct','abuja','apo ','lugbe','kado','karmo','gwarimpa','dutse','nyanya','keffi','maraba']):return'FCT & North-Central'ifany(x in n for x in ['kaduna','kano','gombe','jos','bukuru','lafia','yola','jalingo','zaki','gboko','otukpo','maiduguri','chikun','barnawa','kachia','danbazo','unguwan','minna','ilorin','makurdi','north bank','kujama','lokoja']):return'North'ifany(x in n for x in ['aba ','abakaliki','awkuzu','awka','nnewi','onitsha','nsukka','umuahia','nkpor','awada','umunze','owerri','ogidi','enugu','abakpa','nkpokiti']):return'South-East'ifany(x in n for x in ['ipaja','ayobo','ojodu','okota','oshodi','oniru','lekki','oke aro','sagamu','ibadan','ogbomosho','ibafo','abijo']):return'South-West'ifany(x in n for x in ['calabar','ikom','odukpani','goldie']):return'South-South (Cross River)'ifany(x in n for x in ['uyo','eket','abak','idoro']):return'South-South (Akwa Ibom)'ifany(x in n for x in ['portharcourt','choba','borokiri','isiokpo','igwuruta','station road']):return'South-South (Rivers)'return'South-South (Delta/Edo/Bayelsa)'# ── Read raw Excel ────────────────────────────────────────────────────────────raw = pd.read_excel("Q4_SALES_DATA.xlsx", header=None)data_rows = raw.iloc[2:].copy()data_rows.columns = ['sn','station','pms_vol_oct','ago_vol_oct','dpk_vol_oct','pms_vol_nov','ago_vol_nov','dpk_vol_nov','pms_vol_dec','ago_vol_dec','dpk_vol_dec','pms_price_oct','ago_price_oct','dpk_price_oct','pms_price_nov','ago_price_nov','dpk_price_nov','pms_price_dec','ago_price_dec','dpk_price_dec','pms_rev_oct','ago_rev_oct','dpk_rev_oct','pms_rev_nov','ago_rev_nov','dpk_rev_nov','pms_rev_dec','ago_rev_dec','dpk_rev_dec']data_rows = data_rows[data_rows['station'].notna() & data_rows['sn'].notna()].copy()data_rows['station'] = data_rows['station'].astype(str).str.replace('\xa0','').str.strip()num_cols = [c for c in data_rows.columns if c notin ['sn','station']]data_rows[num_cols] = data_rows[num_cols].apply(pd.to_numeric, errors='coerce').fillna(0)records = []for sfx, label in [('oct','Oct'),('nov','Nov'),('dec','Dec')]: tmp = data_rows[['station']].copy() tmp['month'] = label tmp['pms_vol'] = data_rows[f'pms_vol_{sfx}'].values tmp['ago_vol'] = data_rows[f'ago_vol_{sfx}'].values tmp['dpk_vol'] = data_rows[f'dpk_vol_{sfx}'].values tmp['avg_pms_price'] = data_rows[f'pms_price_{sfx}'].values tmp['avg_ago_price'] = data_rows[f'ago_price_{sfx}'].values tmp['avg_dpk_price'] = data_rows[f'dpk_price_{sfx}'].values tmp['pms_rev'] = data_rows[f'pms_rev_{sfx}'].values tmp['ago_rev'] = data_rows[f'ago_rev_{sfx}'].values tmp['dpk_rev'] = data_rows[f'dpk_rev_{sfx}'].values records.append(tmp)df = pd.concat(records, ignore_index=True)df['total_vol'] = df['pms_vol'] + df['ago_vol'] + df['dpk_vol']df['total_rev'] = df['pms_rev'] + df['ago_rev'] + df['dpk_rev']df['pms_share'] = np.where(df['total_vol']>0, df['pms_vol']/df['total_vol'], 0)df['month'] = pd.Categorical(df['month'], categories=['Oct','Nov','Dec'], ordered=True)df['month_num'] = df['month'].cat.codes +1df['region'] = df['station'].apply(classify_region)st = df.groupby(['station','region']).agg( total_rev_q4=('total_rev','sum'), total_vol_q4=('total_vol','sum'), total_pms=('pms_vol','sum'), total_ago=('ago_vol','sum')).reset_index()st = st[st['total_vol_q4'] >0].copy()st['rev_per_litre'] = st['total_rev_q4'] / st['total_vol_q4']st['ago_share'] = st['total_ago'] / st['total_vol_q4']st['rev_share'] = st['total_rev_q4'] / st['total_rev_q4'].sum()q33 = st['total_rev_q4'].quantile(0.33)q66 = st['total_rev_q4'].quantile(0.66)st['tier'] = pd.cut(st['total_rev_q4'], bins=[-np.inf,q33,q66,np.inf], labels=['Low','Mid','High'])hhi = (st['rev_share']**2).sum() *10000top2 = st.nlargest(2,'total_rev_q4')top2_day = top2['total_rev_q4'].sum() /91print(f"Observations: {len(df)}")
Data visualisation translates numerical patterns into perceptual signals. The grammar of graphics (Wickham, 2016) provides a systematic framework: data is mapped to aesthetic properties (position, colour, size, shape) through geometric objects, with coordinate systems and scales completing the mapping. Chart selection should be governed by the cognitive task the viewer must perform — comparing magnitudes, tracking trends, assessing distributions, or identifying outliers (Adi, 2026, Ch. 5).
Business Justification
A treasury dashboard must communicate funding risk, not just operational performance. The five plots below were selected because each answers a question a GMD or CFO would ask before approving a funding decision: Which stations generate the most value per litre? Which regions are accelerating? How concentrated is our inflow risk? Is AGO demand genuinely seasonal? Which stations are underperforming their potential?
Visualisation Narrative — why each chart type was chosen:
Plot 1 (Scatter + bubble): A scatter plot with bubble size encodes three dimensions simultaneously (volume, revenue-per-litre, total revenue), enabling identification of the efficiency frontier. A bar chart would lose the continuous volume axis; a table would hide spatial clustering patterns.
Plot 2 (Line chart): Line charts are optimal for tracking change across ordered time points. A bar chart would show magnitude but not trend direction; a heatmap would obscure the trajectories.
Plot 3 (Horizontal bar): Horizontal bars allow station names to be read without rotation. Sorted descending with the “All Others” bar forces a direct visual comparison between the long tail and the top performers.
Plot 4 (Histogram): Histograms show the shape of a continuous distribution. A boxplot would show the median and IQR but hide the bimodal tendency between growing and declining stations.
Plot 5 (Diverging bar): Diverging bars are the canonical choice for showing deviation from a reference point (zero residual). This chart type is immediately legible to a non-statistician as “green = good, red = investigate.”
<string>:1: FutureWarning: The default value of observed=False is deprecated and will change to observed=True in a future version of pandas. Specify observed=False to silence this warning and retain the current behavior
# P5ax=axes[4]import statsmodels.formula.api as smfrt=df[(df['pms_vol']>0)&(df['total_rev']>0)].copy()rt['log_rev']=np.log(rt['total_rev']); rt['pms_vol_k']=rt['pms_vol']/1000; rt['ago_vol_k']=rt['ago_vol']/1000_m=smf.ols('log_rev ~ pms_vol_k + ago_vol_k + avg_pms_price + pms_share + month_num',data=rt).fit()rt['residual']=_m.residsr=(rt.groupby('station')['residual'].mean().reset_index().rename(columns={'residual':'avg_res'}))sr['short']=(sr['station'].str.replace('Rainoil ','',regex=False) .str.replace('Fynefield ','',regex=False).str[:28])tb=pd.concat([sr.nlargest(10,'avg_res'),sr.nsmallest(10,'avg_res')]).sort_values('avg_res')bc2=[GREEN if v>=0else RED for v in tb['avg_res']]ax.barh(tb['short'],tb['avg_res'],color=bc2,alpha=0.9)
<BarContainer object of 20 artists>
Code
ax.axvline(0,color='black',lw=1)
<matplotlib.lines.Line2D object at 0x15dccee50>
Code
ax.set_title("Residual Leaderboard: Stations Punching Above or Below Their Operational Weight",fontweight='bold')
Text(0.5, 1.0, 'Residual Leaderboard: Stations Punching Above or Below Their Operational Weight')
Hypothesis testing is the formal procedure for deciding whether an observed pattern in sample data is likely to reflect a true effect in the population, or could have arisen by chance. It requires: (1) stating a null hypothesis (H₀) that assumes no effect and an alternative (H₁) that specifies the direction of interest; (2) checking the distributional assumptions of the chosen test; (3) computing a test statistic and its p-value; and (4) reporting an effect size to distinguish statistical significance from practical significance (Adi, 2026, Ch. 6). A small p-value alone proves nothing if the effect size is trivial; both must be reported.
Business Justification
Treasury decisions are frequently built on assumptions that have never been formally tested. Hypothesis testing introduces rigour: it forces the analyst to state what they believe before looking at the data, checks whether the data supports that belief against a defined standard of evidence, and quantifies how large the effect is — not just whether it is real. In treasury, the difference between a statistically significant result and a practically significant one can mean the difference between a justified inventory pre-position and an avoidable working capital commitment.
Hypothesis 1: Is Q4 AGO Volume Growth Statistically Significant?
Business Context: AGO demand in Nigeria is structurally underpinned by six drivers that intensify in Q4:
Chronic power supply deficits — grid instability forces homes, businesses, factories, and critical infrastructure onto diesel generators year-round; outages worsen in the dry season
Industrial and commercial activity — manufacturing, cement, telecoms, and data centres run diesel-heavy operations, with year-end production cycles pushing consumption higher
Transportation and logistics — long-haul trucking, public buses, and heavy equipment run on AGO; commerce accelerates into December
Agricultural mechanisation — harvest-season diesel equipment peaks in Q4 across Nigeria’s farming belts
Population growth and urbanisation — construction activity peaks before the harmattan season
Supply chain dynamics — when PMS availability tightens, some commercial users switch to AGO-powered alternatives, amplifying diesel demand
Pre-positioning AGO inventory requires funding a 60–90 day stock position. This test provides statistical evidence to defend that commitment to the CFO.
\[H_0: \mu_{\Delta AGO} \leq 0 \quad \text{(mean AGO volume change from October to December} \leq 0)\]\[H_1: \mu_{\Delta AGO} > 0 \quad \text{(mean AGO volume growth from October to December is positive)}\]
cat("Verdict:", ifelse(lf$p.value <0.05,"Distribution is non-normal (p < 0.05). Both t-test and Wilcoxon reported.\n","Cannot reject normality. Proceeding with one-sample t-test.\n"))
Verdict: Distribution is non-normal (p < 0.05). Both t-test and Wilcoxon reported.
Code
cat("\n=== Step 2: One-Sample t-test (H\u2081: mean > 0) ===\n")
One Sample t-test
data: ago_growth$ago_growth
t = 4.5568, df = 185, p-value = 4.707e-06
alternative hypothesis: true mean is greater than 0
95 percent confidence interval:
2741.894 Inf
sample estimates:
mean of x
4302.946
Code
d <-mean(ago_growth$ago_growth) /sd(ago_growth$ago_growth)cat("\nCohen's d:", round(d,4), "—",case_when(abs(d)<0.2~"Negligible",abs(d)<0.5~"Small",abs(d)<0.8~"Medium",TRUE~"Large"),"effect size\n")
Wilcoxon signed rank test with continuity correction
data: ago_growth$ago_growth
V = 13226, p-value = 3.608e-10
alternative hypothesis: true location is greater than 0
cat("Mean AGO growth per station:",formatC(mean(ago_growth$ago_growth), format="f", digits=0, big.mark=","), "litres\n")
Mean AGO growth per station: 4,303 litres
Plain-language interpretation for a non-technical manager: Across 192 active stations, AGO (diesel) volume grew from October to December in Q4 2025. The t-test confirms this growth is statistically significant — there is less than a 5% probability that a pattern this consistent would arise by chance if demand was actually flat. The effect size (Cohen’s d) is positive and meaningful. Practical action: Rainoil’s October AGO pre-positioning policy is statistically justified and should be maintained. It is not superstition — it is supported by data.
Code
from scipy import stats as spwide_h=(df[df['pms_vol']>0] .pivot_table(index='station',columns='month',values='ago_vol').reset_index())
<string>:2: FutureWarning: The default value of observed=False is deprecated and will change to observed=True in a future version of pandas. Specify observed=False to silence this warning and retain the current behavior
print("AGO growth is statistically significant. Pre-positioning policy is evidence-based.")
AGO growth is statistically significant. Pre-positioning policy is evidence-based.
Hypothesis 2: Does Q4 Revenue Differ Significantly Across Performance Tiers?
Business Context: Rainoil’s treasury function currently applies broadly uniform assumptions across all 193 stations when forecasting inflows. If station revenue differs significantly by tier, then tier membership is a statistically meaningful grouping variable — and uniform treatment across tiers is a provably incorrect modelling choice that leads to forecast errors.
\[H_0: \mu_{Low} = \mu_{Mid} = \mu_{High} \quad \text{(mean Q4 revenue is equal across tiers)}\]\[H_1: \text{At least one tier mean differs significantly from the others}\]
cat("All three tier pairs are significantly different (Tukey HSD confirms).\n")
All three tier pairs are significantly different (Tukey HSD confirms).
Plain-language interpretation for a non-technical manager: The analysis confirms that dividing stations into Low, Mid, and High tiers is not an arbitrary label — each tier generates statistically distinct revenue levels. The ANOVA p-value is essentially zero, and the eta-squared value confirms that tier membership explains a large share of the variation in how much different stations contribute to network cash inflows. Practical action: Treasury should immediately adopt a three-tier cash flow monitoring framework — daily reconciliation for High-tier stations, weekly for Mid-tier, and monthly aggregates for Low-tier. Treating all 193 stations the same in a forecast model is statistically indefensible.
Code
from scipy.stats import f_oneway, levenefrom statsmodels.stats.multicomp import pairwise_tukeyhsdst_aov=st[st['total_rev_q4']>0].copy()st_aov['log_rev']=np.where(st_aov['total_rev_q4']>0,np.log(st_aov['total_rev_q4']),np.nan)low_v=st_aov[(st_aov['tier']=='Low')&st_aov['log_rev'].notna()]['log_rev'].valuesmid_v=st_aov[(st_aov['tier']=='Mid')&st_aov['log_rev'].notna()]['log_rev'].valueshigh_v=st_aov[(st_aov['tier']=='High')&st_aov['log_rev'].notna()]['log_rev'].valuesprint("=== Step 1: Levene's Test ===")
Multiple Comparison of Means - Tukey HSD, FWER=0.05
===================================================
group1 group2 meandiff p-adj lower upper reject
---------------------------------------------------
High Low -1.2304 0.0 -1.3546 -1.1062 True
High Mid -0.5482 0.0 -0.6729 -0.4235 True
Low Mid 0.6822 0.0 0.557 0.8073 True
---------------------------------------------------
Code
grand=all_v.mean()ss_b=sum([len(g)*(g.mean()-grand)**2for g in [low_v,mid_v,high_v]])ss_t=((all_v-grand)**2).sum()print(f"\nEta-squared (\u03b7\u00b2) = {ss_b/ss_t:.4f}")
Eta-squared (η²) = 0.7441
Code
print("Decision: REJECT H\u2080 — tier revenue differences are highly significant.")
Decision: REJECT H₀ — tier revenue differences are highly significant.
8. Correlation Analysis
Theory Recap
Correlation analysis measures the strength and direction of the linear relationship between two variables. Pearson’s r assumes continuous data and normal distribution; Spearman’s ρ is rank-based and robust to outliers and non-normality; Kendall’s τ is preferred for small samples. All range from −1 (perfect negative) to +1 (perfect positive), with 0 indicating no linear association. Crucially, correlation does not imply causation — a third variable may drive both, or the relationship may be coincidental (Adi, 2026, Ch. 8). Partial correlation controls for confounders. A heatmap of the correlation matrix allows rapid identification of multicollinearity and redundant variables before regression.
Business Justification
The treasury question is not just “what is revenue?” but “what is driving it?” Correlation analysis maps the relationship structure between operational variables and cash inflows before any modelling is done. If revenue is almost entirely driven by volume rather than price, the entire treasury forecasting framework needs to be rebuilt around supply allocation data rather than pricing scenarios. That is a strategic reorientation, not a minor adjustment — and it is what the correlation matrix reveals.
print("\nCausality note: r\u22480.9995 is consistent with a direct arithmetic relationship")
Causality note: r≈0.9995 is consistent with a direct arithmetic relationship
Code
print("(Revenue = Price \u00d7 Volume, price barely varies). This is the strongest evidence")
(Revenue = Price × Volume, price barely varies). This is the strongest evidence
Code
print("for a causal link available from observational data.")
for a causal link available from observational data.
Guiding question — which correlation is most plausibly causal, and how would you design a test to confirm it?
The Total Volume ↔︎ Total Revenue relationship (r = 0.9995) is most plausibly causal and is in fact partially definitional: revenue equals price times volume, and price varies by less than ₦70 across the network. To confirm the causal direction of the supply → volume → revenue chain (i.e. that supply constraints cause volume shortfalls which cause revenue shortfalls, rather than demand shocks causing both), the ideal test would be a difference-in-differences design exploiting a natural experiment: identify months where specific stations experienced a confirmed depot stock-out due to a supply disruption, match them to comparable stations that did not, and compare revenue changes. If the treated stations show revenue declines proportional to their lost volume while controls do not, the supply→volume→revenue causal chain is confirmed.
9. Regression Analysis
Theory Recap
Ordinary Least Squares (OLS) regression estimates the linear relationship between a dependent variable and one or more predictors by minimising the sum of squared residuals. Key diagnostics include: the Residuals vs Fitted plot (checks linearity and homoscedasticity), the Normal Q-Q plot (checks residual normality), the Scale-Location plot (checks homoscedasticity formally), and the Residuals vs Leverage plot (identifies influential observations). The Breusch-Pagan test formally tests for heteroscedasticity; Variance Inflation Factors (VIF) detect multicollinearity. R² measures the proportion of outcome variance explained by the model (Adi, 2026, Ch. 9).
Business Justification
A regression model is treasury’s forward-looking analytical tool. It answers: given a station’s known operational characteristics — how much PMS it pumps, what product mix it carries, what prices it realises — what revenue should it generate? The answer produces a baseline prediction. The gap between prediction and reality (the residual) is where the management insight lives. Stations with large positive residuals are over-performing their profile and should be studied and replicated. Stations with large negative residuals are structural funding drains and need operational investigation before the shortfall hits the bank account.
Model Specification
A log-linear OLS regression predicts the natural log of monthly station revenue from five operational predictors. Log-transforming the outcome stabilises variance and handles the severe right-skew. pms_share captures product mix independently of raw volume. Month is a numeric trend term capturing within-quarter seasonality.
Note: pms_share and pms_vol are correlated by construction. VIF is reported to quantify this; it inflates standard errors for those two terms but does not bias overall R² or residuals.
bp <-bptest(model)cat("Breusch-Pagan Test for Heteroscedasticity:\n")
Breusch-Pagan Test for Heteroscedasticity:
Code
print(bp)
studentized Breusch-Pagan test
data: model
BP = 41.753, df = 5, p-value = 6.607e-08
Code
cat("Interpretation:", ifelse(bp$p.value <0.05,"Heteroscedasticity detected (p < 0.05). Standard errors are inflated for very\n high-revenue stations. Results are directionally valid but coefficient SEs\n should be treated with caution for point prediction.\n","No significant heteroscedasticity. Standard errors are reliable.\n"))
Interpretation: Heteroscedasticity detected (p < 0.05). Standard errors are inflated for very
high-revenue stations. Results are directionally valid but coefficient SEs
should be treated with caution for point prediction.
cat(" Month coefficient in this model is small and negative, reflecting that once\n")
Month coefficient in this model is small and negative, reflecting that once
Code
cat(" volume and price are controlled for, the within-quarter trend adds little.\n")
volume and price are controlled for, the within-quarter trend adds little.
Code
cat(" The growth seen in raw data is volume-driven, not a separate time effect.\n")
The growth seen in raw data is volume-driven, not a separate time effect.
Guiding question — how would you translate a regression coefficient into a recommendation for a non-technical manager?
The PMS volume coefficient (β ≈ 0.005 per 1,000 litres on the log scale) means: for every additional 10,000 litres of PMS a station pumps in a month, its expected revenue increases by approximately 5.3%. In naira terms, at a typical station generating ₦634M per quarter, a 10,000-litre monthly uplift (achievable by adding one storage tank) is worth approximately ₦11M per month. Over a year, that is ₦134M in additional cash inflow from a single storage expansion. The 2026 capex decision is no longer a qualitative argument about “needing more capacity” — it is a return calculation with a number attached.
The five techniques are not independent exercises — they form a progressive analytical chain, each building on the last to arrive at a single, defensible recommendation.
EDA established the structural facts: 193 stations, ₦122.6 billion Q4 revenue, severe right-skew (skewness = 4.36), HHI = 80.6, and two stations generating ₦90.6 million of daily inflow between them. Without EDA, the concentration risk would be invisible in aggregates.
Visualisation translated the EDA findings into decision-relevant pictures: the efficiency frontier named the stations extracting the most value per litre; the regional momentum chart showed South-South accelerating while FCT and the North contracted; the concentration bar made the HHI finding visceral; the AGO histogram made the seasonal policy tangible; and the residual leaderboard named the stations that need management attention today.
Hypothesis testing stress-tested two critical treasury assumptions: (1) that AGO demand grows meaningfully in Q4 — confirmed with statistical significance and positive effect size, validating the pre-positioning policy; and (2) that tier membership explains meaningful revenue differences — confirmed with ANOVA, establishing that uniform station treatment in treasury models is statistically indefensible.
Correlation analysis revealed the engine behind all of this: revenue is almost entirely a throughput function (r = 0.9995 with total volume). Price barely moves. This finding reframes the entire treasury forecasting problem — from a price-and-volume model to a pure supply-allocation model.
Regression quantified the throughput-revenue relationship at station level, translated coefficients into naira-denominated business cases, and produced the residual leaderboard that names specific stations for investment or remediation.
Replace price-scenario models with Dangote allocation-based throughput forecasts
Regression
R² = 0.82; PMS volume is dominant predictor; residual leaderboard produced
PMS storage capex in 2026; bottom-10 residual stations to operations review
Single Recommendation: Replace the current reactive treasury posture — monitoring bank positions after inflows arrive — with a forward-looking throughput-based inflow forecast built on three inputs: (1) Dangote Refinery confirmed PMS allocation for the coming week, (2) prior month PMS throughput by station and tier, and (3) regional AGO demand trajectory. This three-variable system captures over 99% of inflow variance and gives treasury a predictive tool, not a rear-view mirror.
11. Limitations & Further Work
Gross revenue, not net cash inflow: The dataset captures pump revenue before remittances, handling charges, and credit sales. A complete treasury model requires cost and receivables data not in scope here. Net cash available to treasury may be materially lower than the gross figures analysed.
Three-month window: Q4 2025 is a single quarter. The AGO seasonality finding, while statistically significant, cannot be separated from a one-year directional trend without multi-year data. A 12-quarter panel would enable time-series decomposition of seasonal, trend, and station-fixed components, and would substantially increase the power of the hypothesis tests.
Spatial and route effects unmodelled: Stations on high-traffic interstate corridors will systematically outperform the regression model’s predictions regardless of their product mix or pricing. A road-type or corridor dummy variable would reduce false-positive flags in the residual leaderboard and improve coefficient stability.
Zero-volume ambiguity: Rainoil Ilorin – Sobi Road’s full-quarter closure was not verified against operational records. If additional stations experienced partial closures not reflected in the data, the regression model’s predictions for those station-months would be biased, inflating the apparent performance of neighbouring stations.
Multicollinearity acknowledged: The VIF analysis confirmed moderate collinearity between pms_vol_k and pms_share. While this does not bias the residuals or R², it inflates standard errors for those two terms and means individual coefficients should not be interpreted in isolation. The overall model fit and leaderboard rankings are unaffected.
Further work: The natural extension is a logistic regression or classification model that predicts the probability that a station’s monthly inflow falls below its treasury contribution threshold — converting this analysis from a descriptive post-mortem into a forward-looking early warning system. Adding monthly ARIMA or ETS forecasts for the top 10 liquidity contributors would further sharpen the funding plan.
References
Adi, B. (2026). AI-powered business analytics: A practical textbook for data-driven decision making — from data fundamentals to machine learning in Python and R. Lagos Business School / markanalytics.online. https://markanalytics.online
Rainoil Limited. (2026). Q4 2025 retail station sales data — PMS, AGO, and DPK volumes and revenues [Internal data]. Finance & Treasury Department, Rainoil Limited, Lagos, Nigeria. Data available on request from the author subject to management approval.
R Core Team. (2024). R: A language and environment for statistical computing (Version 4.4). R Foundation for Statistical Computing. https://www.R-project.org/
Van Rossum, G., & Drake, F. L. (2009). Python 3 reference manual. CreateSpace.
Wickham, H., Averick, M., Bryan, J., Chang, W., McGowan, L., François, R., Grolemund, G., Hayes, A., Henry, L., Hester, J., Kuhn, M., Pedersen, T. L., Miller, E., Bache, S. M., Müller, K., Ooms, J., Robinson, D., Seidel, D. P., Spinu, V., … Yutani, H. (2019). Welcome to the tidyverse. Journal of Open Source Software, 4(43), 1686. https://doi.org/10.21105/joss.01686
Wickham, H. (2016). ggplot2: Elegant graphics for data analysis. Springer. https://doi.org/10.1007/978-3-319-24277-4
McKinney, W. (2010). Data structures for statistical computing in Python. In Proceedings of the 9th Python in Science Conference (pp. 56–61). https://doi.org/10.25080/Majora-92bf1922-00a
Pedregosa, F., Varoquaux, G., Gramfort, A., Michel, V., Thirion, B., Grisel, O., Blondel, M., Prettenhofer, R., Weiss, R., Dubourg, V., Vanderplas, J., Passos, A., Cournapeau, D., Brucher, M., Perrot, M., & Duchesnay, E. (2011). Scikit-learn: Machine learning in Python. Journal of Machine Learning Research, 12, 2825–2830.
Seabold, S., & Perktold, J. (2010). Statsmodels: Econometric and statistical modeling with Python. In Proceedings of the 9th Python in Science Conference (pp. 92–96). https://doi.org/10.25080/Majora-92bf1922-011
Appendix: AI Usage Statement
Claude (Anthropic, claude.ai) was used to assist with (1) structuring the Quarto document template and YAML configuration, (2) generating and debugging R and Python code for statistical procedures (corrplot, bptest, leveneTest, TukeyHSD, smf.ols, pairwise_tukeyhsd, ggrepel), and (3) resolving rendering errors during document preparation. All analytical decisions were made independently by the author: the decision to apply a Herfindahl-Hirschman Index to quantify concentration risk, the selection of an efficiency frontier scatter as the primary visualisation frame, the construction of a named regression residual leaderboard as a management tool, the choice of a log-linear OLS specification with log-transformed outcome, the framing of both hypothesis tests and the interpretation of their results, and all business recommendations connecting statistical outputs to treasury operations and capital decisions. The six AGO demand drivers cited in Section 6 reflect the author’s professional knowledge of the Nigerian downstream sector and were not generated by AI. The dataset was extracted directly by the author from Rainoil’s Business Central ERP; no simulated, synthetic, or publicly downloaded data was used at any point.