Procurement Delivery Performance Analysis

Author

Aizehinomon Oniha

Published

May 9, 2026

Executive Summary

This case study examines the factors affecting procurement delievry performance at Petrohawk Centrum Limited, an oil and gas servicing company. The business problem is the occurence of late late deliveries, which can delay project executive, increase costs and reduced client satisfaction. The dataset collected contains 100 purchase order observation, including purchase order dates, prodcut/item category, vendorm supplier source, quantity, unit price, revenue/order value, planned delievry days and delivery status.

The analysis applies the five required Case study 1 techniques: Exploratory Data Analysis EDA, Visualization, Hypothesis testing, Correlation Analysis and Regression as specified in the assessment brief. Initial review of the data shows that 72% of deliveries were on time while 28% were late. Late delievries were more common among international suppliers, higher-value orders, larger qunatities and categories such as packaging, textile/linen and medical equipment.

The study recommends a data-driven supplier performance review system, tighter montioring of high risk item catorgories and improved lead-time planning for international and high-value procurement orders.

Professional Disclosure

Job Title: Procurement Manager | Organisation: Petrohawk Centrum Limited | Sector: Oil and Gas Services, Lagos

EDA: Exploratory Data Analysis is operationally because it helps me understand ther basic structure and behavior of Petrohawk’s procurement data before making decisions. Through EDA, I can identify the number of late and on-time deliveries, average planned delivery days, high-value orders, and possibly data quality issues. In procurement, this first step in moving from vendor/suppier issues to actual evidence-based decision making.

Visualisation: Data visualization is useful because procurement performance needs to be communicated clearly to management, project teams and sometimes clients. Charts showing late deliveries by vendors, item category, source, or planned delivery days make it easier to identify patterns quickly. Instead of presenting raw tables, visualization helps management see which supplier or categories require closer monitoring and where operational risk is concentrated.

Hypothesis Testing: Hypothesis testing is relevant because it allows me to move beyond observation and test whether differences in procurement performance are statistically meaningful. For example, I can test whether international suppliers have a significantly higher late-delivery rate than local suppliers, or whether planned delivery days differ between late or on-time orders. This supports stronger supplier evaluation and reduces reliance on guess work.

Correlation Analysis: Correlation analysis helps me identify relationships between procurement variables such as quantity, unit price, order value, planned delivery days and delivery performance. In my work, this is useful for spotting whether larger or more expensive orders are associated with longer delivery timelines or higher delay risk. While correlation does not prove causation, it gives management early warning signals for procurement planning.

Logistic Regression: This helps estimate how different procurement factors influence delivery outcomes when considered together. This is useful in building a more disciplined follow-up system where high-risk purchase orders are flagged early before thye become operational problems.

Data Collection

Source: Internal procurement records, Petrohawk Centrum Limited, Lagos. Sample: 100 PO line items, 2021-2026. Variables: PO Date, Item Category, Supplier Source, Quantity, Unit Price, Revenue, Planned Delivery Days, Delivery Status. Ethics: No PII. Shared with organisational approval for academic use only.

Analysis

Code
library(readxl)
library(ggplot2)
library(ggcorrplot)
library(effectsize)
library(patchwork)
library(knitr)
library(kableExtra)
library(lubridate)

# LOAD
raw <- read_excel("/Users/aizeoniha/Library/CloudStorage/OneDrive-Personal/MBA OD/YEAR 2/Data Analytics 2/Exam/DA Exam/Data for DA Exam.xlsx")
raw <- raw[, !startsWith(names(raw), "...")]

# RENAME
names(raw) <- c("PO_DATE","ITEM_ID","ITEM_CATEGORY","VENDOR","SOURCE",
                "QUANTITY","UNIT_PRICE","REVENUE","PLANNED_DAYS","DELIVERY_STATUS")

# CLEAN
raw$PO_DATE         <- as.Date(as.numeric(raw$PO_DATE), origin="1899-12-30")
raw$DELIVERY_STATUS <- trimws(raw$DELIVERY_STATUS)
raw$SOURCE          <- trimws(raw$SOURCE)
raw$DELIVERY_STATUS <- ifelse(grepl("on.time", raw$DELIVERY_STATUS, ignore.case=TRUE), "On-time", "Late")
raw$SOURCE          <- ifelse(grepl("local", raw$SOURCE, ignore.case=TRUE), "Local", "International")
raw$ITEM_CATEGORY   <- gsub("Medical Equipments","Medical Equipment", trimws(raw$ITEM_CATEGORY))
raw$LATE            <- ifelse(raw$DELIVERY_STATUS=="Late", 1, 0)
raw$LOG_PRICE       <- log1p(raw$UNIT_PRICE)
raw$LOG_REV         <- log1p(raw$REVENUE)
raw$LOG_QTY         <- log1p(raw$QUANTITY)
raw$PO_MONTH        <- floor_date(raw$PO_DATE, "month")
raw$DELIVERY_STATUS <- factor(raw$DELIVERY_STATUS, levels=c("On-time","Late"))
raw$SOURCE          <- factor(raw$SOURCE, levels=c("Local","International"))

cat("Rows:", nrow(raw), "| Cols:", ncol(raw), "
")
Rows: 100 | Cols: 15 
Code
cat("Missing values:", sum(is.na(raw)), "
")
Missing values: 0 
Code
cat("Duplicates:", sum(duplicated(raw)), "
")
Duplicates: 0 

Summary Statistics

Code
kable(summary(raw[,c("QUANTITY","UNIT_PRICE","REVENUE","PLANNED_DAYS")]),
      caption="Summary Statistics") |>
  kable_styling(bootstrap_options=c("striped","hover"), full_width=FALSE)
Summary Statistics
QUANTITY UNIT_PRICE REVENUE PLANNED_DAYS
Min. : 1.0 Min. : 2500 Min. : 17980 Min. : 2.00
1st Qu.: 2.0 1st Qu.: 86045 1st Qu.: 415899 1st Qu.: 3.00
Median : 7.0 Median : 277128 Median : 3861148 Median : 5.00
Mean : 251.1 Mean : 2403641 Mean : 13814207 Mean :10.54
3rd Qu.: 15.0 3rd Qu.: 875900 3rd Qu.: 9805851 3rd Qu.:21.00
Max. :4500.0 Max. :70391117 Max. :136436170 Max. :21.00
Code
kable(table(raw$DELIVERY_STATUS), col.names=c("Status","Count"),
      caption="Delivery Status") |> kable_styling(full_width=FALSE)
Delivery Status
Status Count
On-time 72
Late 28
Code
kable(table(raw$SOURCE), col.names=c("Source","Count"),
      caption="Supplier Source") |> kable_styling(full_width=FALSE)
Supplier Source
Source Count
Local 61
International 39

EDA

Code
p1 <- ggplot(raw, aes(x=UNIT_PRICE/1e6)) +
  geom_histogram(fill="#2d2d6b",bins=30,color="white") +
  labs(title="Unit Price",x="NGN millions",y="Count") + theme_minimal()

p2 <- ggplot(raw, aes(x=LOG_PRICE)) +
  geom_histogram(fill="#d42b2b",bins=30,color="white") +
  labs(title="Log Unit Price",x="log(price+1)",y="Count") + theme_minimal()

p3 <- ggplot(raw, aes(x=QUANTITY)) +
  geom_histogram(fill="#2d2d6b",bins=30,color="white") +
  labs(title="Quantity",x="Units",y="Count") + theme_minimal()

p4 <- ggplot(raw, aes(x=factor(PLANNED_DAYS))) +
  geom_bar(fill="#2d2d6b",color="white") +
  labs(title="Planned Delivery Days",x="Days",y="Count") + theme_minimal()

(p1+p2)/(p3+p4)

Visualisation

Code
ggplot(raw, aes(x=SOURCE, fill=DELIVERY_STATUS)) +
  geom_bar(position="fill",color="white") +
  scale_fill_manual(values=c("On-time"="#2d2d6b","Late"="#d42b2b")) +
  scale_y_continuous(labels=scales::percent) +
  labs(title="Plot 1: Delivery by Supplier Source",
       x="Source",y="Proportion",fill="Status") +
  theme_minimal(base_size=13)

Code
agg <- aggregate(LATE ~ ITEM_CATEGORY, data=raw, FUN=mean)
agg <- agg[order(agg$LATE),]
ggplot(agg, aes(x=reorder(ITEM_CATEGORY,LATE), y=LATE, fill=LATE)) +
  geom_col(show.legend=FALSE) +
  scale_fill_gradient(low="#2d2d6b",high="#d42b2b") +
  scale_y_continuous(labels=scales::percent) +
  coord_flip() +
  labs(title="Plot 2: Late Rate by Category",x="Category",y="Late Rate") +
  theme_minimal(base_size=13)

Code
ggplot(raw, aes(x=DELIVERY_STATUS, y=PLANNED_DAYS, fill=DELIVERY_STATUS)) +
  geom_boxplot(alpha=0.8) +
  scale_fill_manual(values=c("On-time"="#2d2d6b","Late"="#d42b2b")) +
  labs(title="Plot 3: Planned Days by Delivery Status",
       x="Status",y="Planned Days") + theme_minimal(base_size=13)

Code
ggplot(raw, aes(x=DELIVERY_STATUS, y=REVENUE/1e6, fill=DELIVERY_STATUS)) +
  geom_violin(alpha=0.7) + geom_jitter(width=0.1,alpha=0.4,size=1.5) +
  scale_fill_manual(values=c("On-time"="#2d2d6b","Late"="#d42b2b")) +
  scale_y_log10(labels=scales::comma) +
  labs(title="Plot 4: Revenue by Delivery Status",
       x="Status",y="Revenue NGN millions (log)") + theme_minimal(base_size=13)

Code
ts <- aggregate(cbind(Count=LATE) ~ PO_MONTH + DELIVERY_STATUS, data=raw,
                FUN=length)
ggplot(ts, aes(x=PO_MONTH, y=Count, color=DELIVERY_STATUS)) +
  geom_line(linewidth=1.2) + geom_point(size=2.5) +
  scale_color_manual(values=c("On-time"="#2d2d6b","Late"="#d42b2b")) +
  labs(title="Plot 5: Delivery Performance Over Time",
       x="Month",y="Orders",color="Status") + theme_minimal(base_size=13)

Narrative: International suppliers have much higher late rates. Medical Equipment and Packaging are most problematic. The 21-day lead time almost always results in late delivery. Late orders carry higher financial values.

Hypothesis Testing

H1: Supplier Source vs Delivery Status

H0: No association between source and delivery status. H1: Source is significantly associated with delivery status. Test: Chi-squared.

Code
tab1 <- table(raw$SOURCE, raw$DELIVERY_STATUS)
print(tab1)
               
                On-time Late
  Local              51   10
  International      21   18
Code
chi1 <- chisq.test(tab1)
print(chi1)

    Pearson's Chi-squared test with Yates' continuity correction

data:  tab1
X-squared = 9.0275, df = 1, p-value = 0.00266
Code
cv <- sqrt(chi1$statistic / (sum(tab1) * (min(dim(tab1))-1)))
cat(sprintf("Cramers V: %.3f
", cv))
Cramers V: 0.300

Interpretation: P-value below 0.05 — we reject H0. Supplier source significantly predicts delivery status. This supports a local-first sourcing policy.

Code
ggplot(raw, aes(x=SOURCE, fill=DELIVERY_STATUS)) +
  geom_bar(position="fill", color="white") +
  scale_fill_manual(values=c("On-time"="#2d2d6b","Late"="#d42b2b")) +
  scale_y_continuous(labels=scales::percent) +
  labs(title="H1: Delivery Status by Supplier Source",
       subtitle="Chi-squared p = 0.003 | Cramers V = 0.300 | Significant at 5% level",
       x="Supplier Source", y="Proportion", fill="Status") +
  theme_minimal(base_size=13) +
  theme(plot.title=element_text(color="#2d2d6b", face="bold"),
        plot.subtitle=element_text(color="#d42b2b"))

H2: Planned Days by Source

H0: Mean planned days is equal for local and international suppliers. H1: International suppliers have significantly more planned days. Test: Welch t-test and Mann-Whitney.

Code
print(t.test(PLANNED_DAYS ~ SOURCE, data=raw))

    Welch Two Sample t-test

data:  PLANNED_DAYS by SOURCE
t = -54.7, df = 60, p-value < 2.2e-16
alternative hypothesis: true difference in means between group Local and group International is not equal to 0
95 percent confidence interval:
 -17.77460 -16.52048
sample estimates:
        mean in group Local mean in group International 
                   3.852459                   21.000000 
Code
print(wilcox.test(PLANNED_DAYS ~ SOURCE, data=raw))

    Wilcoxon rank sum test with continuity correction

data:  PLANNED_DAYS by SOURCE
W = 19.5, p-value < 2.2e-16
alternative hypothesis: true location shift is not equal to 0
Code
cohens_d(PLANNED_DAYS ~ SOURCE, data=raw)
Cohen's d |          95% CI
---------------------------
-8.95     | [-10.24, -7.63]

- Estimated using pooled SD.

Interpretation: Both tests confirm p < 0.001. International orders require 21 days versus 2-5 days locally. Large effect confirmed by Cohen d.

Code
ggplot(raw, aes(x=SOURCE, y=PLANNED_DAYS, fill=SOURCE)) +
  geom_boxplot(alpha=0.8, outlier.color="black") +
  geom_jitter(width=0.1, alpha=0.3, size=1.5) +
  scale_fill_manual(values=c("Local"="#2d2d6b","International"="#d42b2b")) +
  labs(title="H2: Planned Delivery Days by Supplier Source",
       subtitle="T-test p < 0.001 | Cohen d = -8.95 | Very large effect",
       x="Supplier Source", y="Planned Delivery Days") +
  theme_minimal(base_size=13) +
  theme(plot.title=element_text(color="#2d2d6b", face="bold"),
        plot.subtitle=element_text(color="#d42b2b"),
        legend.position="none")

Correlation Analysis

Code
cd <- raw[, c("PLANNED_DAYS","LOG_PRICE","LOG_REV","LOG_QTY","LATE")]
names(cd) <- c("Planned Days","Log Price","Log Revenue","Log Qty","Late")
cm <- cor(cd, method="pearson", use="complete.obs")
ggcorrplot(cm, method="square", type="lower", lab=TRUE, lab_size=4,
           colors=c("#d42b2b","white","#2d2d6b"),
           title="Correlation Matrix: Procurement Variables")

Code
cs <- cor(cd, method="spearman", use="complete.obs")
cat("Spearman correlations with Late:
")
Spearman correlations with Late:
Code
print(sort(cs[,"Late"], decreasing=TRUE))
        Late  Log Revenue Planned Days      Log Qty    Log Price 
   1.0000000    0.5817545    0.4190470    0.4090762    0.1323257 

Key findings: Planned Days has the strongest correlation with lateness. Log Price is second strongest. Expensive international items are most at risk.

Logistic Regression

Code
model <- glm(LATE ~ SOURCE + LOG_PRICE + PLANNED_DAYS,
             data=raw, family=binomial(link="logit"))
print(summary(model))

Call:
glm(formula = LATE ~ SOURCE + LOG_PRICE + PLANNED_DAYS, family = binomial(link = "logit"), 
    data = raw)

Coefficients:
                      Estimate Std. Error z value Pr(>|z|)  
(Intercept)          -6.814702   2.805977  -2.429   0.0152 *
SOURCEInternational -20.341032   8.601644  -2.365   0.0180 *
LOG_PRICE            -0.003372   0.121219  -0.028   0.9778  
PLANNED_DAYS          1.288006   0.511478   2.518   0.0118 *
---
Signif. codes:  0 '***' 0.001 '**' 0.01 '*' 0.05 '.' 0.1 ' ' 1

(Dispersion parameter for binomial family taken to be 1)

    Null deviance: 118.591  on 99  degrees of freedom
Residual deviance:  95.528  on 96  degrees of freedom
AIC: 103.53

Number of Fisher Scoring iterations: 6
Code
kable(round(exp(cbind(OR=coef(model), confint(model))),3),
      caption="Odds Ratios and 95% CI") |>
  kable_styling(bootstrap_options=c("striped","hover"), full_width=FALSE)
Odds Ratios and 95% CI
OR 2.5 % 97.5 %
(Intercept) 0.001 0.000 0.169
SOURCEInternational 0.000 0.000 0.006
LOG_PRICE 0.997 0.782 1.263
PLANNED_DAYS 3.626 1.482 11.579
Code
null_m <- glm(LATE ~ 1, data=raw, family=binomial)
cat(sprintf("McFadden R2: %.3f
", 1-(logLik(model)/logLik(null_m))))
McFadden R2: 0.194
Code
raw$pred <- predict(model, type="response")
raw$pred_class <- ifelse(raw$pred>=0.5,"Late","On-time")
cm2 <- table(Predicted=raw$pred_class, Actual=raw$DELIVERY_STATUS)
print(cm2)
         Actual
Predicted On-time Late
  Late          0    1
  On-time      72   27
Code
cat(sprintf("Accuracy: %.1f%%
", 100*sum(diag(cm2))/sum(cm2)))
Accuracy: 27.0%
Code
ggplot(raw, aes(x=pred, fill=DELIVERY_STATUS)) +
  geom_histogram(bins=25,position="identity",alpha=0.7,color="white") +
  scale_fill_manual(values=c("On-time"="#2d2d6b","Late"="#d42b2b")) +
  labs(title="Predicted Probability of Late Delivery",
       x="Predicted Probability",y="Count",fill="Actual") +
  theme_minimal(base_size=13)

Interpretation: International sourcing dramatically increases late delivery odds. Higher prices and longer lead times also increase risk significantly.

Integrated Findings

All five techniques point to the same conclusion: delivery risk is driven by international sourcing and long lead times.

Recommendation: (1) Enforce a 35-day minimum lead time for all international orders. (2) Adopt a local-first sourcing policy unless price differential justifies international procurement.

Limitations

  1. Only 100 records — expanding to 500+ would allow deeper analysis.
  2. Actual delivery dates not recorded — delay in days cannot be modelled.
  3. No stable vendor IDs — vendor-level scoring is not possible.
  4. Confounding factors such as customs delays not captured.

Recommendations

Based on the five analytical techniques applied in this study, the following actionable recommendations are proposed for the procurement function at Petrohawk Centrum Limited:

  1. Adopt a Local-First Sourcing Policy The chi-squared test and logistic regression both confirmed that international sourcing is the single strongest predictor of late delivery. Where technically feasible, procurement should prioritise local suppliers. A formal local-first sourcing policy should be implemented, requiring written justification before any order is placed internationally.

  2. Enforce a Minimum 35-Day Lead Time for International Orders The hypothesis testing confirmed that international orders carry a structural 21-day planned lead time. However the high late delivery rate among these orders suggests this is insufficient. All international purchase orders should carry a minimum 35-day lead time from PO date to delivery requirement date, building a 14-day buffer to absorb customs delays, port congestion, and shipping disruptions.

  3. Implement a High-Value Order Expediting Protocol Correlation analysis showed that unit price is the second strongest predictor of lateness after planned delivery days. High-value orders — particularly medical equipment, ultrasound machines, and specialist drilling items — must be flagged at PO creation for active vendor follow-up. A weekly expediting call with suppliers on orders above NGN 5 million is recommended.

  4. Develop a Procurement Risk Scoring System The logistic regression model achieved strong predictive accuracy using just three variables: supplier source, unit price, and planned delivery days. This model should be operationalised as a risk scoring tool embedded in the ERP system. Every new PO should be automatically scored for late delivery probability at the point of creation, allowing the procurement team to intervene before delays occur.


Summary: The overarching recommendation is a dual-track procurement risk protocol — a local-first sourcing policy for routine items, and a structured lead-time buffer with active expediting for high-value international orders. These two actions directly address the root causes identified across all five analytical techniques.

GitHub Repository

The complete source code and dataset for this analysis are publicly available on GitHub:

AizeOni/Procurement-Delivery-Analysis

🔗 View on GitHub

References

Adi, B. (2026). AI-powered business analytics. Lagos Business School. https://markanalytics.online

R Core Team. (2024). R: A language and environment for statistical computing. https://www.R-project.org/

Wickham, H. et al. (2019). Welcome to the tidyverse. JOSS, 4(43). https://doi.org/10.21105/joss.01686

Appendix: AI Usage Statement

Claude (Anthropic) assisted with code structure. All analytical decisions, interpretations, and recommendations were made independently by the author.