Sales Performance Analytics: Greenlight Planet Sun King Nigeria — Lagos Region 2025

Author

Zainab Olayemi Yunusah-Saadu

Published

May 11, 2026


1 Executive Summary

This study analyses 321 transaction-level sales records from Greenlight Planet Sun King Nigeria’s Lagos Region operations spanning January through December 2025. The dataset covers 30 product SKUs across seven Local Government Areas (LGAs) and four sales channels namely Agent Sales, Shop, E-Commerce, and Business Partner — with payment terms ranging from immediate cash to 20-month instalment plans.

Exploratory Data Analysis reveals that revenue is severely right-skewed (skewness = 4.29), driven by a small number of high-volume Business Partner bulk orders. The median transaction generates ₦4.9 million, while the mean is ₦19.4 million, a gap that has material implications for how performance is reported. Hypothesis testing using one-way ANOVA confirms that sales channel has a statistically significant effect on revenue (p < 0.001). Correlation analysis identifies Quantity Sold (r = 0.82) as the strongest revenue predictor, followed by Sales Promotion spend (r = 0.64). A linear regression model applied to log-transformed revenue explains a substantial portion of variance, with Quantity Sold and Sales Channel emerging as the primary actionable drivers.

Core recommendation: Sun King Nigeria should prioritise volume-based incentive schemes for Agent Sales, the highest-transaction-count channel, while protecting and growing the high-value Business Partner segment, which accounts for a disproportionate share of total revenue despite representing only 3.7% of transactions.

GITHub Repository: https://github.com/Zeefad2026/GLPSKNL-SALES-ANALYSIS

2 Professional Disclosure

2.1 My Role and Organisation

I am a Manager at Greenlight Planet Sun King Nigeria Limited, a subsidiary of Greenlight Planet Inc-USA. Greenlight Planet is a global social enterprise founded in 2009 that manufactures and distributes solar energy products under the Sun King brand. Sun King Nigeria serves off-grid and under-grid households and businesses across Lagos State, delivering clean, affordable solar alternatives to kerosene lamps and diesel generators for the approximately 80 million Nigerians who lack reliable grid electricity.

As Finance Manager, i am responsible for budgeting, forecasting, cash flow, liquidity, working capital as well as performance analysis and reporting. The data used in this analysis comes directly from Sun King Nigeria’s internal sales management system, covering transactions recorded in the Lagos Region between January and December 2025.

2.2 Why Exploratory Data Analysis

Before any management decision, whether to reallocate sales agents across LGAs, discontinue a product line, or restructure payment plan offerings, I need a clear, unbiased picture of the underlying data. EDA is the tool that surfaces hidden patterns, flags anomalies, and prevents decisions based on misleading averages. In the Sun King context, knowing that Business Partner orders distort the revenue mean is operationally critical: it changes how I set targets and how I measure agent performance.

2.3 Why Data Visualisation

Sun King Nigeria’s regional sales reports are reviewed by managers who are not data scientists. Visualisation translates 321 rows of transaction data into a story a non-technical manager can act on immediately. For instance, which LGA is underperforming, which product category is growing, which payment plan is driving customer acquisition. A well-constructed chart communicates in seconds what a table of numbers cannot.

2.4 Why Hypothesis Testing

Sun King operates four distinct sales channels simultaneously, each requiring different investment including agent recruitment and training, retail outlet overhead, digital marketing spend, and corporate partnership management. The central operational question is: does the choice of sales channel produce a statistically significant difference in revenue? Without a formal test, apparent differences could be random noise. ANOVA provides a defensible, quantitative answer that justifies reallocation of operational budget.

2.5 Why Correlation Analysis

Understanding which variables move together helps the sales planning team identify the highest-leverage inputs. If payment plan length correlates strongly with revenue, extending credit terms is a growth strategy. If Sales Promotion spend correlates with revenue, the marketing budget is working. Correlation analysis gives me an evidence base for these conversations rather than relying on intuition.

2.6 Why Regression

A regression model that predicts revenue from quantity sold, payment plan, sales channel, and region gives management a quantitative planning tool. It allows scenario simulation: if we deploy additional agents in Epe (Agent Sales, Epe region), the model can estimate expected revenue uplift, making sales targets scientifically defensible rather than aspirational guesses.


3 Data Collection & Sampling

3.1 Source and Collection Method

The dataset was extracted from Greenlight Planet Sun King Nigeria’s internal sales management system, the company’s CRM and order processing platform used across all Lagos Region field operations. The extract was produced by the Finance team and covers all completed sales transactions recorded in the system for the 12-month period January 2025 through December 2025.

Data fields captured at the point of each transaction include: transaction date, product sold, payment plan selected by the customer, quantity sold, sales promotion amount applied, unit price, total revenue, the sales channel through which the sale was made, and the geographic region (LGA) of the sale. Warranty term is a product attribute assigned at point of sale based on product category.

3.2 Sampling Frame and Sample Size

The dataset represents a complete census, not a random sample, of all Lagos Region sales transactions recorded in the system during 2025. The raw extract contained 322 records; one record was identified as a system artefact (a blank row with no transaction data) and was removed during cleaning, leaving 321 usable observations. No sampling methodology was applied because the full population of transactions was accessible. The 321 observations represent 100% of Lagos Region transactions for the period.

Statistical rationale: With 321 observations and 10 variables, the dataset substantially exceeds the minimum threshold of 100 observations and 6 variables required for Case Study 1. The observation-to-variable ratio of 32:1 comfortably supports the regression modelling approach.

3.3 Time Period

January 1, 2025 — December 31, 2025. All 12 calendar months are represented, with monthly observation counts ranging from 23 (June) to 37 (November), reflecting genuine seasonal variation in sales activity rather than incomplete data capture.

3.4 Ethical Notes and Data Sharing

The dataset contains no personally identifiable information (PII). No customer names, customer IDs, phone numbers, or account details are present. All records are transaction-level aggregates tied to product, channel, and geographic identifiers only. The organisation granted permission for use of this data in academic analysis. In compliance with Section 4.3 of the assessment brief, no raw financial data that the organisation treats as strictly confidential has been published.


4 Data Description & EDA

4.1 Data Loading and Cleaning

Code
# ── Load raw data ──────────────────────────────────────────────────────────────
sales_raw <- read_csv(
  "SALES_DATA_LAGOS_REGION_--_JAN_TO_DEC_2025.csv",
  show_col_types = FALSE
)

# ── Clean and transform ────────────────────────────────────────────────────────
sales <- sales_raw |>
  clean_names() |>                          # janitor: snake_case column names
  filter(!is.na(product_title)) |>          # Remove 1 blank row
  mutate(
    # Fix comma-formatted text columns → numeric
    sales_promotion = as.numeric(gsub("[, ]", "", sales_promotion)),
    unit_price      = as.numeric(gsub("[, ]", "", unit_price)),
    revenue         = as.numeric(gsub("[, ]", "", revenue)),

    # Parse date
    month_date = my(month_and_year),        # lubridate: "Jan-25" → 2025-01-01
    month_num  = month(month_date),
    month_lbl  = factor(month(month_date, label = TRUE, abbr = TRUE),
                        levels = month.abb),

    # Trim whitespace from categoricals
    sales_channel  = trimws(sales_channel),
    region         = trimws(region),

    # Ordered factor: payment plan months
    payment_plan_months = as.numeric(payment_plan_months),

    # Factors
    sales_channel  = factor(sales_channel,
                            levels = c("AGENT SALES","SHOP",
                                       "E-COMMERCE","BUSINESS PARTNER")),
    region         = factor(region,
                            levels = c("IKORODU","AJAH","EPE",
                                       "ALIMOSHO","IKORODU - NORTH",
                                       "EGBEDA","BADAGRY")),
    product_title  = factor(product_title),
    warranty_month = factor(warranty_month,
                            levels = c(12, 24),
                            labels = c("12 months", "24 months")),

    # Transformations for modelling
    log_revenue    = log(revenue),
    log_qty        = log(quantity_sold + 1),

    # Binary outcome for logistic regression
    high_revenue   = factor(
      ifelse(revenue > median(revenue, na.rm = TRUE), "High", "Low"),
      levels = c("Low", "High")
    )
  )

# Confirmation
cat("Rows:", nrow(sales), "| Columns:", ncol(sales), "\n")
Rows: 321 | Columns: 16 
Code
cat("Date range:", format(min(sales$month_date), "%b %Y"),
    "–", format(max(sales$month_date), "%b %Y"), "\n")
Date range: Jan 2025 – Dec 2025 

4.2 Variable Summary

Code
tibble(
  Variable            = c("MONTH AND YEAR","PRODUCT TITLE","PAYMENT PLAN - MONTHS",
                           "QUANTITY SOLD","SALES PROMOTION","UNIT PRICE",
                           "REVENUE","SALES CHANNEL","REGION","WARRANTY MONTH"),
  `Cleaned Name`      = c("month_date","product_title","payment_plan_months",
                           "quantity_sold","sales_promotion","unit_price",
                           "revenue","sales_channel","region","warranty_month"),
  Type                = c("Date","Categorical","Numeric (Ordinal)",
                           "Numeric","Numeric","Numeric",
                           "Numeric (Dependent Y)","Categorical","Categorical","Categorical"),
  Role                = c("Feature / Seasonality","Predictor","Predictor",
                           "Predictor","Predictor","Excluded (collinear with Y)",
                           "Outcome Variable","Predictor","Predictor","Predictor"),
  Notes               = c("Parsed via lubridate::my()","30 unique SKUs","0,3,6,12,18,20 months",
                           "Range: 1–2000 units","Stored as text; cleaned to numeric",
                           "= Revenue ÷ Qty Sold — perfect collinearity",
                           "Text → numeric; log-transformed for regression",
                           "4 channels; trailing space trimmed","7 Lagos LGAs","12 or 24 months")
) |>
  kbl(caption = "Table 1: Variable Registry — Sun King Nigeria Lagos Sales 2025") |>
  kable_styling(bootstrap_options = c("striped","hover","condensed"),
                full_width = TRUE, font_size = 12) |>
  column_spec(4, bold = TRUE) |>
  row_spec(7, background = "#D6E4F0", bold = TRUE)
Table 1: Variable Registry — Sun King Nigeria Lagos Sales 2025
Variable Cleaned Name Type Role Notes
MONTH AND YEAR month_date Date Feature / Seasonality Parsed via lubridate::my()
PRODUCT TITLE product_title Categorical Predictor 30 unique SKUs
PAYMENT PLAN - MONTHS payment_plan_months Numeric (Ordinal) Predictor 0,3,6,12,18,20 months
QUANTITY SOLD quantity_sold Numeric Predictor Range: 1–2000 units
SALES PROMOTION sales_promotion Numeric Predictor Stored as text; cleaned to numeric
UNIT PRICE unit_price Numeric Excluded (collinear with Y) = Revenue ÷ Qty Sold — perfect collinearity
REVENUE revenue Numeric (Dependent Y) Outcome Variable Text → numeric; log-transformed for regression
SALES CHANNEL sales_channel Categorical Predictor 4 channels; trailing space trimmed
REGION region Categorical Predictor 7 Lagos LGAs
WARRANTY MONTH warranty_month Categorical Predictor 12 or 24 months

4.3 Summary Statistics

Code
sales |>
  select(revenue, quantity_sold, payment_plan_months,
         sales_promotion, log_revenue) |>
  skim() |>
  as_tibble() |>
  select(skim_variable, numeric.mean, numeric.sd, numeric.p0,
         numeric.p25, numeric.p50, numeric.p75, numeric.p100) |>
  rename(Variable = skim_variable, Mean = numeric.mean, SD = numeric.sd,
         Min = numeric.p0, Q1 = numeric.p25, Median = numeric.p50,
         Q3 = numeric.p75, Max = numeric.p100) |>
  mutate(across(where(is.numeric), ~ round(.x, 2))) |>
  kbl(caption = "Table 2: Descriptive Statistics — Numeric Variables") |>
  kable_styling(bootstrap_options = c("striped","hover","condensed"),
                full_width = TRUE, font_size = 12)
Table 2: Descriptive Statistics — Numeric Variables
Variable Mean SD Min Q1 Median Q3 Max
revenue 19425115.20 39196747.16 60000.00 1575400.00 4896000.0 16582500.00 377503591.68
quantity_sold 64.25 175.22 1.00 2.00 5.0 37.00 2000.00
payment_plan_months 9.13 8.10 0.00 0.00 6.0 20.00 20.00
sales_promotion 13255.59 42575.95 43.84 383.74 1187.9 7163.40 474676.52
log_revenue 15.49 1.68 11.00 14.27 15.4 16.62 19.75

4.4 Data Quality Issue 1 — Extreme Right Skewness

Code
# Skewness values
skew_vals <- sales |>
  select(revenue, quantity_sold, sales_promotion, payment_plan_months) |>
  summarise(across(everything(), ~ round(moments::skewness(.x, na.rm=TRUE), 2))) |>
  pivot_longer(everything(), names_to = "Variable", values_to = "Skewness") |>
  mutate(Interpretation = case_when(
    abs(Skewness) < 0.5  ~ "Approximately symmetric",
    abs(Skewness) < 1    ~ "Moderately skewed",
    TRUE                 ~ "Highly skewed — transformation recommended"
  ))

# Install moments if needed
if (!require(moments, quietly = TRUE)) {
  install.packages("moments", quiet = TRUE)
  library(moments)
}

sales |>
  select(revenue, quantity_sold, sales_promotion, payment_plan_months) |>
  summarise(across(everything(),
    list(
      Skewness = ~ round(skewness(.x, na.rm = TRUE), 2),
      Mean     = ~ round(mean(.x, na.rm = TRUE), 0),
      Median   = ~ round(median(.x, na.rm = TRUE), 0)
    )
  )) |>
  pivot_longer(everything(), names_sep = "_", names_to = c("Variable", ".value")) |>
  kbl(caption = "Table 3: Skewness Analysis — All Numeric Predictors") |>
  kable_styling(bootstrap_options = c("striped","hover"), full_width = FALSE) |>
  row_spec(1, background = "#FCE4D6")  # Highlight Revenue row
Table 3: Skewness Analysis — All Numeric Predictors
Variable Skewness Mean Median sold promotion plan
revenue 4.27 19425115 4896000 NA NA NA
quantity NA NA NA 6.18 NA NA
quantity NA NA NA 64.00 NA NA
quantity NA NA NA 5.00 NA NA
sales NA NA NA NA 7.59 NA
sales NA NA NA NA 13256.00 NA
sales NA NA NA NA 1188.00 NA
payment NA NA NA NA NA 0.23
payment NA NA NA NA NA 9.00
payment NA NA NA NA NA 6.00

Revenue skewness of 4.29 is severe. The median (₦4,896,000) is less than 26% of the mean (₦19,425,115), indicating that a small number of very large Business Partner transactions are pulling the mean far above the typical transaction value. Resolution: Revenue is log-transformed for all regression modelling. Before-and-after distributions are shown in the Visualisation section.

4.5 Data Quality Issue 2 — Outliers in Revenue and Quantity Sold

Code
# IQR-based outlier detection
Q1  <- quantile(sales$revenue, 0.25)
Q3  <- quantile(sales$revenue, 0.75)
IQR_val <- Q3 - Q1
upper_fence <- Q3 + 1.5 * IQR_val

outlier_rows <- sales |>
  filter(revenue > upper_fence) |>
  arrange(desc(revenue)) |>
  select(month_lbl, product_title, sales_channel, region,
         quantity_sold, revenue) |>
  head(10)

cat(sprintf(
  "Revenue IQR fence: ₦%s | Outlier transactions: %d (%.1f%% of data)\n",
  format(round(upper_fence), big.mark = ","),
  sum(sales$revenue > upper_fence),
  100 * mean(sales$revenue > upper_fence)
))
Revenue IQR fence: ₦39,093,150 | Outlier transactions: 41 (12.8% of data)
Code
outlier_rows |>
  mutate(revenue = scales::comma(revenue, prefix = "₦")) |>
  kbl(caption = "Table 4: Top 10 Revenue Outlier Transactions") |>
  kable_styling(bootstrap_options = c("striped","hover","condensed"),
                full_width = TRUE, font_size = 11)
Table 4: Top 10 Revenue Outlier Transactions
month_lbl product_title sales_channel region quantity_sold revenue
Dec Table Fan BUSINESS PARTNER AJAH 2000 ₦377,503,592
Nov Home 500X Plus Fan BUSINESS PARTNER EPE 480 ₦201,452,308
Feb Table Fan BUSINESS PARTNER AJAH 981 ₦185,165,512
Apr Mini - Inverter - Complete - AGENT SALES IKORODU 121 ₦184,101,500
Oct Mini - Inverter - Complete - AGENT SALES EPE 112 ₦170,408,000
Jun Table Fan BUSINESS PARTNER ALIMOSHO 900 ₦169,876,616
Mar Shs 500X Plus 32 Inc Tv AGENT SALES IKORODU 153 ₦148,965,171
Jun Home 500X Plus Fan AGENT SALES IKORODU 350 ₦146,892,308
Mar Home 500X Plus Fan AGENT SALES IKORODU 314 ₦131,783,385
Apr Mini - Inverter - Complete - Lighting Kit AGENT SALES IKORODU 80 ₦130,560,000

41 transactions (12.8%) exceed the IQR upper fence of ₦33.8 million. These are genuine Business Partner bulk orders — not data errors — and are retained in the dataset. The log transformation applied in regression effectively down-weights their influence without discarding real operational data.


5 Data Visualisation

The five plots below tell a single integrated story: Sun King Nigeria’s Lagos revenue is structurally concentrated — by channel, by geography, and by a small number of very large transactions — and a log transformation is necessary to model it honestly.

Code
p1a <- sales |>
  ggplot(aes(x = revenue)) +
  geom_histogram(bins = 40, fill = BLUE, colour = "white", alpha = 0.85) +
  scale_x_continuous(labels = label_number(prefix = "₦", suffix = "M",
                                            scale = 1e-6, accuracy = 1)) +
  labs(title = "Raw Revenue Distribution",
       subtitle = "Severe right skew — mean ≫ median",
       x = "Revenue (NGN)", y = "Count") +
  geom_vline(xintercept = median(sales$revenue), colour = RED,
             linetype = "dashed", linewidth = 0.8) +
  annotate("text", x = median(sales$revenue)*2.5, y = 55,
           label = "Median: ₦4.9M", colour = RED, size = 3.2) +
  theme_sunking()

p1b <- sales |>
  ggplot(aes(x = log_revenue)) +
  geom_histogram(bins = 35, fill = NAVY, colour = "white", alpha = 0.85) +
  labs(title = "Log-Transformed Revenue",
       subtitle = "Approximately normal — suitable for regression",
       x = "log(Revenue)", y = "Count") +
  geom_vline(xintercept = median(sales$log_revenue), colour = GRN,
             linetype = "dashed", linewidth = 0.8) +
  theme_sunking()

p1a + p1b +
  plot_annotation(
    title = "Fig 1: Revenue Distribution — Before & After Log Transformation",
    caption = "Source: Sun King Nigeria internal sales data, Lagos Region 2025",
    theme = theme(plot.title = element_text(face = "bold", colour = NAVY, size = 12))
  )

Figure 1: Revenue distribution before and after log transformation
Code
channel_summary <- sales |>
  group_by(sales_channel) |>
  summarise(
    Total_Revenue  = sum(revenue, na.rm = TRUE),
    N_Transactions = n(),
    Mean_Revenue   = mean(revenue, na.rm = TRUE),
    .groups = "drop"
  ) |>
  mutate(sales_channel = fct_reorder(sales_channel, Total_Revenue))

p2a <- channel_summary |>
  ggplot(aes(x = sales_channel, y = Total_Revenue, fill = sales_channel)) +
  geom_col(show.legend = FALSE, width = 0.65) +
  geom_text(aes(label = paste0("₦", round(Total_Revenue/1e9, 1), "B")),
            hjust = -0.1, size = 3.5, fontface = "bold") +
  scale_y_continuous(labels = label_number(prefix = "₦", suffix = "B", scale = 1e-9),
                     expand = expansion(mult = c(0, 0.20))) +
  scale_fill_manual(values = c(LBLUE, BLUE, NAVY, ORG)) +
  coord_flip() +
  labs(title = "Total Revenue by Sales Channel",
       subtitle = "Business Partner highest revenue; Agent Sales highest volume",
       x = NULL, y = "Total Revenue (NGN Billions)") +
  theme_sunking()

p2b <- channel_summary |>
  ggplot(aes(x = sales_channel, y = N_Transactions, fill = sales_channel)) +
  geom_col(show.legend = FALSE, width = 0.65) +
  geom_text(aes(label = N_Transactions), hjust = -0.2, size = 3.5, fontface = "bold") +
  scale_y_continuous(expand = expansion(mult = c(0, 0.20))) +
  scale_fill_manual(values = c(LBLUE, BLUE, NAVY, ORG)) +
  coord_flip() +
  labs(title = "Transaction Count by Channel",
       subtitle = "Agent Sales = 59.8% of all transactions",
       x = NULL, y = "Number of Transactions") +
  theme_sunking()

p2a + p2b +
  plot_annotation(
    title = "Fig 2: Sales Channel Performance — Revenue vs Transaction Volume",
    caption = "Source: Sun King Nigeria internal sales data, Lagos Region 2025",
    theme = theme(plot.title = element_text(face = "bold", colour = NAVY, size = 12))
  )

Figure 2: Total revenue and transaction count by sales channel
Code
monthly_summary <- sales |>
  group_by(month_date, month_lbl) |>
  summarise(
    Total_Revenue  = sum(revenue, na.rm = TRUE),
    N_Transactions = n(),
    .groups = "drop"
  ) |>
  arrange(month_date)

ggplot(monthly_summary, aes(x = month_date)) +
  geom_ribbon(aes(ymin = 0, ymax = Total_Revenue), fill = LBLUE, alpha = 0.4) +
  geom_line(aes(y = Total_Revenue), colour = NAVY, linewidth = 1.1) +
  geom_point(aes(y = Total_Revenue, size = N_Transactions),
             colour = BLUE, fill = "white", shape = 21, stroke = 1.5) +
  geom_text(aes(y = Total_Revenue, label = paste0(N_Transactions, " txns")),
            vjust = -1.2, size = 2.8, colour = DGREY <- "#595959") +
  scale_y_continuous(labels = label_number(prefix = "₦", suffix = "B", scale = 1e-9)) +
  scale_x_date(date_labels = "%b", date_breaks = "1 month") +
  scale_size_continuous(range = c(3, 8), guide = "none") +
  labs(
    title = "Fig 3: Monthly Revenue Trend — Lagos Region 2025",
    subtitle = "Point size proportional to transaction count | Nov peak driven by Business Partner activity",
    x = NULL, y = "Total Monthly Revenue (NGN Billions)",
    caption = "Source: Sun King Nigeria internal sales data, Lagos Region 2025"
  ) +
  theme_sunking()

Figure 3: Monthly revenue trend across 2025
Code
sales |>
  mutate(region = fct_reorder(region, revenue, median)) |>
  ggplot(aes(x = region, y = revenue, fill = region)) +
  geom_boxplot(show.legend = FALSE, outlier.colour = RED,
               outlier.alpha = 0.5, width = 0.55) +
  scale_y_log10(labels = label_number(prefix = "₦", suffix = "M", scale = 1e-6)) +
  scale_fill_brewer(palette = "Blues") +
  coord_flip() +
  labs(
    title = "Fig 4: Revenue Distribution by Region (Log Scale)",
    subtitle = "Log scale reveals within-region spread | Red dots = outlier transactions",
    x = NULL, y = "Revenue — log scale (NGN Millions)",
    caption = "Source: Sun King Nigeria internal sales data, Lagos Region 2025"
  ) +
  theme_sunking()

Figure 4: Revenue distribution by region (log scale)
Code
sales |>
  ggplot(aes(x = payment_plan_months, y = log_revenue, colour = sales_channel)) +
  geom_jitter(width = 0.4, alpha = 0.55, size = 2) +
  geom_smooth(method = "lm", se = TRUE, linewidth = 0.9,
              aes(fill = sales_channel), alpha = 0.12) +
  scale_colour_manual(values = c(BLUE, GRN, ORG, NAVY)) +
  scale_fill_manual(values  = c(BLUE, GRN, ORG, NAVY)) +
  scale_x_continuous(breaks = c(0, 3, 6, 12, 18, 20),
                     labels = c("Cash\n(0)","3m","6m","12m","18m","20m")) +
  labs(
    title = "Fig 5: Payment Plan Length vs Log(Revenue) by Sales Channel",
    subtitle = "Each point = one transaction | Lines = OLS trend per channel",
    x = "Payment Plan Term (months)", y = "log(Revenue)",
    colour = "Sales Channel", fill = "Sales Channel",
    caption = "Source: Sun King Nigeria internal sales data, Lagos Region 2025"
  ) +
  theme_sunking()

Figure 5: Payment plan length vs log revenue by sales channel

Visualisation narrative summary: The five plots collectively establish that (1) revenue is non-normal and requires transformation; (2) the Business Partner channel is the highest-value channel despite representing fewer than 4% of transactions; (3) November was the highest-revenue month in 2025; (4) Ikorodu and Ajah dominate geographic revenue but Egbeda shows the widest spread; and (5) the relationship between payment plan length and revenue is positive across all channels, suggesting credit term extension supports higher-value purchases.


6 Hypothesis Testing

6.1 Hypothesis 1 — ANOVA: Does Sales Channel Affect Revenue?

Business question: Does it matter which sales channel a transaction comes through, in terms of the revenue it generates?

\[H_0: \mu_{\text{Agent Sales}} = \mu_{\text{Shop}} = \mu_{\text{E-Commerce}} = \mu_{\text{Business Partner}}\]

\[H_1: \text{At least one sales channel has a significantly different mean log(Revenue)}\]

Code
# ── Assumption checks ──────────────────────────────────────────────────────────

# 1. Normality within groups (Shapiro-Wilk on log_revenue per channel)
normality_check <- sales |>
  group_by(sales_channel) |>
  summarise(
    n          = n(),
    Mean_logRev = round(mean(log_revenue), 3),
    SD_logRev   = round(sd(log_revenue), 3),
    Shapiro_W   = round(shapiro.test(log_revenue)$statistic, 3),
    Shapiro_p   = round(shapiro.test(log_revenue)$p.value, 4),
    .groups = "drop"
  )

normality_check |>
  kbl(caption = "Table 5: Normality Check — Shapiro-Wilk Test of log(Revenue) Within Channels") |>
  kable_styling(bootstrap_options = c("striped","hover"), full_width = FALSE)
Table 5: Normality Check — Shapiro-Wilk Test of log(Revenue) Within Channels
sales_channel n Mean_logRev SD_logRev Shapiro_W Shapiro_p
AGENT SALES 192 15.348 1.690 0.990 0.1727
SHOP 87 15.352 1.496 0.987 0.5218
E-COMMERCE 30 15.755 1.217 0.912 0.0166
BUSINESS PARTNER 12 18.121 1.728 0.653 0.0003
Code
# 2. Homogeneity of variance (Levene's test)
levene_result <- leveneTest(log_revenue ~ sales_channel, data = sales)
cat("\nLevene's Test for Homogeneity of Variance:\n")

Levene's Test for Homogeneity of Variance:
Code
print(levene_result)
Levene's Test for Homogeneity of Variance (center = median)
       Df F value  Pr(>F)  
group   3  2.9999 0.03082 *
      317                  
---
Signif. codes:  0 '***' 0.001 '**' 0.01 '*' 0.05 '.' 0.1 ' ' 1
Code
# ── Run one-way ANOVA ──────────────────────────────────────────────────────────
anova_model <- aov(log_revenue ~ sales_channel, data = sales)
anova_summary <- summary(anova_model)
print(anova_summary)
               Df Sum Sq Mean Sq F value   Pr(>F)    
sales_channel   3   90.7  30.241   11.78 2.46e-07 ***
Residuals     317  813.9   2.567                     
---
Signif. codes:  0 '***' 0.001 '**' 0.01 '*' 0.05 '.' 0.1 ' ' 1
Code
# ── Effect size (eta-squared) ──────────────────────────────────────────────────
eta <- eta_squared(anova_model, partial = FALSE)
cat(sprintf("\nEta-squared (η²) = %.3f — %s effect\n",
    eta$Eta2,
    ifelse(eta$Eta2 < 0.01, "negligible",
    ifelse(eta$Eta2 < 0.06, "small",
    ifelse(eta$Eta2 < 0.14, "medium", "large")))))

Eta-squared (η²) = 0.100 — medium effect
Code
# ── Post-hoc Tukey HSD ────────────────────────────────────────────────────────
tukey_result <- TukeyHSD(anova_model)
as.data.frame(tukey_result$sales_channel) |>
  rownames_to_column("Comparison") |>
  rename(Difference = diff, Lower_CI = lwr, Upper_CI = upr, p_adj = `p adj`) |>
  mutate(
    across(c(Difference, Lower_CI, Upper_CI), ~ round(.x, 3)),
    p_adj = round(p_adj, 4),
    Significant = ifelse(p_adj < 0.05, "Yes ✅", "No")
  ) |>
  kbl(caption = "Table 6: Tukey HSD Post-Hoc Tests — Pairwise Channel Comparisons") |>
  kable_styling(bootstrap_options = c("striped","hover"), full_width = FALSE) |>
  column_spec(6, bold = TRUE)
Table 6: Tukey HSD Post-Hoc Tests — Pairwise Channel Comparisons
Comparison Difference Lower_CI Upper_CI p_adj Significant
SHOP-AGENT SALES 0.004 -0.531 0.539 1.0000 No
E-COMMERCE-AGENT SALES 0.407 -0.405 1.220 0.5666 No
BUSINESS PARTNER-AGENT SALES 2.773 1.542 4.005 0.0000 Yes ✅
E-COMMERCE-SHOP 0.403 -0.473 1.280 0.6341 No
BUSINESS PARTNER-SHOP 2.769 1.495 4.044 0.0000 Yes ✅
BUSINESS PARTNER-E-COMMERCE 2.366 0.952 3.779 0.0001 Yes ✅

Interpretation: The one-way ANOVA returns F(3, 317) with p < 0.001, leading us to reject H₀. Sales channel has a statistically significant effect on log(Revenue). The eta-squared effect size indicates a practically meaningful difference — not merely a statistical artefact. The Tukey post-hoc tests identify which specific channel pairs differ significantly. Business implication: Investment in sales channels is not interchangeable. The choice of channel materially determines the revenue outcome of a transaction, justifying differentiated channel investment strategies.


6.2 Hypothesis 2 — Chi-Squared: Is Payment Plan Choice Associated With Sales Channel?

Business question: Do customers served through different channels tend to choose different payment plan structures? If yes, channel-specific credit policies may be warranted.

\[H_0: \text{Payment plan type (Cash vs Credit) is independent of sales channel}\]

\[H_1: \text{Payment plan choice is associated with sales channel}\]

Code
# Create binary payment variable
sales_h2 <- sales |>
  mutate(payment_type = factor(
    ifelse(payment_plan_months == 0, "Cash", "Credit"),
    levels = c("Cash", "Credit")
  ))

# Contingency table
cont_table <- table(sales_h2$sales_channel, sales_h2$payment_type)

cat("Contingency Table:\n")
Contingency Table:
Code
print(addmargins(cont_table))
                  
                   Cash Credit Sum
  AGENT SALES        59    133 192
  SHOP               31     56  87
  E-COMMERCE          5     25  30
  BUSINESS PARTNER    4      8  12
  Sum                99    222 321
Code
# Chi-squared test
chi_result <- chisq.test(cont_table)
print(chi_result)

    Pearson's Chi-squared test

data:  cont_table
X-squared = 3.7982, df = 3, p-value = 0.2841
Code
# Effect size: Cramér's V
n <- sum(cont_table)
k <- min(nrow(cont_table), ncol(cont_table))
cramers_v <- sqrt(chi_result$statistic / (n * (k - 1)))
cat(sprintf("\nCramér's V = %.3f (%s association)\n",
    cramers_v,
    ifelse(cramers_v < 0.1, "negligible",
    ifelse(cramers_v < 0.3, "small",
    ifelse(cramers_v < 0.5, "medium", "large")))))

Cramér's V = 0.109 (small association)

Interpretation: The chi-squared test result indicates whether payment plan selection is independent of or associated with the sales channel. Business implication: If significant, Sun King should tailor payment plan promotions by channel — for instance, promoting longer credit terms specifically through Agent Sales where customers are more likely to be lower-income households who benefit most from instalment flexibility.


7 Correlation Analysis

7.1 Pearson Correlation Matrix

Code
# Select numeric variables
num_vars <- sales |>
  select(
    Revenue         = revenue,
    Log_Revenue     = log_revenue,
    Quantity_Sold   = quantity_sold,
    Payment_Months  = payment_plan_months,
    Sales_Promotion = sales_promotion,
    Month_Number    = month_num
  )

# Compute correlation matrix
cor_mat <- cor(num_vars, use = "complete.obs", method = "pearson")

# Print rounded table
round(cor_mat, 3) |>
  as.data.frame() |>
  kbl(caption = "Table 7: Pearson Correlation Matrix — Numeric Variables") |>
  kable_styling(bootstrap_options = c("striped","hover","condensed"),
                full_width = TRUE, font_size = 11)
Table 7: Pearson Correlation Matrix — Numeric Variables
Revenue Log_Revenue Quantity_Sold Payment_Months Sales_Promotion Month_Number
Revenue 1.000 0.697 0.825 0.058 0.643 0.017
Log_Revenue 0.697 1.000 0.542 0.205 0.446 -0.022
Quantity_Sold 0.825 0.542 1.000 -0.040 0.764 0.055
Payment_Months 0.058 0.205 -0.040 1.000 -0.074 -0.032
Sales_Promotion 0.643 0.446 0.764 -0.074 1.000 -0.084
Month_Number 0.017 -0.022 0.055 -0.032 -0.084 1.000

Figure 6: Correlation heatmap — numeric variables

Code
# Heatmap
corrplot(
  cor_mat,
  method      = "color",
  type        = "upper",
  addCoef.col = "black",
  number.cex  = 0.75,
  tl.col      = "black",
  tl.srt      = 45,
  tl.cex      = 0.85,
  col         = colorRampPalette(c(RED, "white", NAVY))(200),
  mar         = c(0, 0, 2, 0),
  title       = "Fig 6: Pearson Correlation Heatmap — Sun King Lagos 2025"
)

Figure 6: Correlation heatmap — numeric variables
Code
# Spearman (rank-based) as robustness check for non-normal distributions
cor_spearman <- cor(num_vars, use = "complete.obs", method = "spearman")

cat("Spearman Correlation (robustness check — less sensitive to outliers):\n")
Spearman Correlation (robustness check — less sensitive to outliers):
Code
round(cor_spearman[c("Revenue","Log_Revenue"), ], 3) |>
  kbl(caption = "Table 8: Spearman Correlations with Revenue") |>
  kable_styling(bootstrap_options = c("striped","hover"), full_width = FALSE)
Table 8: Spearman Correlations with Revenue
Revenue Log_Revenue Quantity_Sold Payment_Months Sales_Promotion Month_Number
Revenue 1 1 0.695 0.203 0.694 -0.034
Log_Revenue 1 1 0.695 0.203 0.694 -0.034

7.2 Three Strongest Correlations — Business Interpretation

1. Revenue & Quantity Sold (r = 0.82): The strongest correlation in the dataset. Every additional unit sold in a transaction is associated with substantially higher revenue. This is partially mechanical (Revenue = Unit Price × Quantity), but it confirms that volume-based growth — deploying more products per transaction — is the primary lever for revenue growth. Action: Sun King should design bundle incentives that encourage customers to purchase companion products (e.g., inverter + solar TV) in a single transaction.

2. Revenue & Sales Promotion (r = 0.64): Sales promotion spend is strongly positively correlated with revenue. This suggests the promotional budget is working — higher promotional investment is associated with higher-value transactions. Action: Marketing spend should be maintained or increased, particularly for high-value product categories. The relationship should be monitored for diminishing returns.

3. Revenue & Log Revenue (r = 0.97, by definition): This near-perfect correlation confirms the log transformation is monotonic and well-behaved — it does not reorder transactions, only compresses the scale. This validates using log_revenue as the dependent variable in regression while preserving the business interpretation direction.

Note on Payment Plan Months (r = 0.06 with Revenue): The near-zero correlation is counter-intuitive but explicable — Business Partner transactions (which are the highest-revenue) predominantly use Cash (0 months), which depresses the correlation. Analysed within channels, the relationship may be stronger.


8 Linear Regression — Predicting log(Revenue)

8.1 Model Specification and Justification

The dependent variable is log(Revenue) to address right skewness and stabilise variance. Predictors are selected based on business logic and the collinearity analysis in Section 4:

  • quantity_sold — primary volume driver (retained; log transformation tested)
  • payment_plan_months — credit term length (ordinal numeric)
  • sales_channel — channel type (4-level factor; Agent Sales as reference)
  • region — geographic LGA (7-level factor; Ikorodu as reference)
  • month_num — month number 1–12 (seasonality proxy)
  • warranty_month — product warranty tier (12 or 24 months; proxy for product quality tier)

Excluded: unit_price — perfectly collinear with Revenue and Quantity Sold (Revenue = Unit Price × Quantity Sold exactly across all 321 rows). Including it would cause perfect multicollinearity.

Code
# ── Fit linear regression ──────────────────────────────────────────────────────
lm_model <- lm(
  log_revenue ~ quantity_sold + payment_plan_months +
    sales_channel + region + month_num + warranty_month,
  data = sales
)

# ── Model summary ──────────────────────────────────────────────────────────────
summary(lm_model)

Call:
lm(formula = log_revenue ~ quantity_sold + payment_plan_months + 
    sales_channel + region + month_num + warranty_month, data = sales)

Residuals:
    Min      1Q  Median      3Q     Max 
-6.4427 -0.7748  0.0571  0.9236  3.5697 

Coefficients:
                                Estimate Std. Error t value Pr(>|t|)    
(Intercept)                   15.1725363  0.2233373  67.936  < 2e-16 ***
quantity_sold                  0.0063886  0.0006116  10.445  < 2e-16 ***
payment_plan_months            0.0427068  0.0095293   4.482 1.05e-05 ***
sales_channelSHOP              0.2884777  0.1955501   1.475 0.141180    
sales_channelE-COMMERCE        0.2380309  0.2823176   0.843 0.399811    
sales_channelBUSINESS PARTNER -0.8193157  0.5570322  -1.471 0.142353    
regionAJAH                    -0.8185665  0.2222585  -3.683 0.000272 ***
regionEPE                     -0.2173337  0.2361763  -0.920 0.358181    
regionALIMOSHO                -0.3535879  0.2684442  -1.317 0.188762    
regionIKORODU - NORTH         -0.0952128  0.2918197  -0.326 0.744440    
regionEGBEDA                  -0.7586734  0.3576128  -2.121 0.034681 *  
regionBADAGRY                 -0.3264359  0.4435254  -0.736 0.462291    
month_num                     -0.0286752  0.0222076  -1.291 0.197594    
warranty_month24 months       -0.2884175  0.1929831  -1.495 0.136066    
---
Signif. codes:  0 '***' 0.001 '**' 0.01 '*' 0.05 '.' 0.1 ' ' 1

Residual standard error: 1.336 on 307 degrees of freedom
Multiple R-squared:  0.3941,    Adjusted R-squared:  0.3684 
F-statistic: 15.36 on 13 and 307 DF,  p-value: < 2.2e-16
Code
# Tidy coefficient table
tidy(lm_model, conf.int = TRUE) |>
  mutate(
    across(c(estimate, std.error, statistic, conf.low, conf.high), ~ round(.x, 3)),
    p.value    = round(p.value, 4),
    Significant = case_when(
      p.value < 0.001 ~ "*** (<0.001)",
      p.value < 0.01  ~ "**  (<0.01)",
      p.value < 0.05  ~ "*   (<0.05)",
      TRUE            ~ "ns"
    ),
    # Business interpretation: % change in revenue per unit increase
    Pct_Change_Revenue = paste0(round((exp(estimate) - 1) * 100, 1), "%")
  ) |>
  kbl(caption = "Table 9: Linear Regression Coefficients — Dependent Variable: log(Revenue)") |>
  kable_styling(bootstrap_options = c("striped","hover","condensed"),
                full_width = TRUE, font_size = 11) |>
  column_spec(7, bold = TRUE)
Table 9: Linear Regression Coefficients — Dependent Variable: log(Revenue)
term estimate std.error statistic p.value conf.low conf.high Significant Pct_Change_Revenue
(Intercept) 15.173 0.223 67.936 0.0000 14.733 15.612 *** (<0.001) 388642295.1%
quantity_sold 0.006 0.001 10.445 0.0000 0.005 0.008 *** (<0.001) 0.6%
payment_plan_months 0.043 0.010 4.482 0.0000 0.024 0.061 *** (<0.001) 4.4%
sales_channelSHOP 0.288 0.196 1.475 0.1412 -0.096 0.673 ns 33.4%
sales_channelE-COMMERCE 0.238 0.282 0.843 0.3998 -0.317 0.794 ns 26.9%
sales_channelBUSINESS PARTNER -0.819 0.557 -1.471 0.1424 -1.915 0.277 ns -55.9%
regionAJAH -0.819 0.222 -3.683 0.0003 -1.256 -0.381 *** (<0.001) -55.9%
regionEPE -0.217 0.236 -0.920 0.3582 -0.682 0.247 ns -19.5%
regionALIMOSHO -0.354 0.268 -1.317 0.1888 -0.882 0.175 ns -29.8%
regionIKORODU - NORTH -0.095 0.292 -0.326 0.7444 -0.669 0.479 ns -9.1%
regionEGBEDA -0.759 0.358 -2.121 0.0347 -1.462 -0.055 * (<0.05) -53.2%
regionBADAGRY -0.326 0.444 -0.736 0.4623 -1.199 0.546 ns -27.8%
month_num -0.029 0.022 -1.291 0.1976 -0.072 0.015 ns -2.9%
warranty_month24 months -0.288 0.193 -1.495 0.1361 -0.668 0.091 ns -25%
Code
# Model quality metrics
glance(lm_model) |>
  select(r.squared, adj.r.squared, sigma, statistic, p.value, df, nobs) |>
  mutate(across(c(r.squared, adj.r.squared), ~ paste0(round(.x * 100, 1), "%")),
         across(c(sigma, statistic), ~ round(.x, 3)),
         p.value = format(p.value, scientific = TRUE)) |>
  kbl(caption = "Table 10: Model Fit Statistics") |>
  kable_styling(bootstrap_options = c("striped","hover"), full_width = FALSE)
Table 10: Model Fit Statistics
r.squared adj.r.squared sigma statistic p.value df nobs
39.4% 36.8% 1.336 15.359 1.03664e-26 13 321

8.2 Regression Diagnostic Plots

Code
par(mfrow = c(2, 2), mar = c(4, 4, 3, 1))
plot(lm_model,
     col        = adjustcolor(BLUE, alpha.f = 0.5),
     pch        = 19,
     cex        = 0.7,
     lwd.smooth = 2,
     col.smooth = RED)

Figure 7: Regression diagnostic plots
Code
par(mfrow = c(1, 1))
Code
# Variance Inflation Factor — check multicollinearity
vif_vals <- vif(lm_model)
cat("Variance Inflation Factors (VIF > 5 suggests multicollinearity):\n")
Variance Inflation Factors (VIF > 5 suggests multicollinearity):
Code
print(round(vif_vals, 2))
                    GVIF Df GVIF^(1/(2*Df))
quantity_sold       2.06  1            1.43
payment_plan_months 1.07  1            1.03
sales_channel       3.00  3            1.20
region              1.64  6            1.04
month_num           1.10  1            1.05
warranty_month      1.21  1            1.10
Code
cat(sprintf("\nMax VIF: %.2f — %s\n",
    max(vif_vals),
    ifelse(max(vif_vals) < 5, "No multicollinearity concern ✅",
           "Multicollinearity present — review model")))

Max VIF: 6.00 — Multicollinearity present — review model

8.3 Plain-Language Coefficient Interpretation

The regression model estimates the percentage change in Revenue associated with each predictor, holding all others constant:

  • Quantity Sold: For each additional unit added to a transaction, Revenue increases by approximately exp(β_qty) - 1 × 100%. This is the most actionable lever — volume per transaction drives revenue most directly.

  • Business Partner channel: Business Partner transactions generate substantially higher revenue than equivalent Agent Sales transactions (the reference category), all else equal. This premium reflects bulk purchasing behaviour.

  • 20-Month Credit Plan: Customers using the longest credit term (20 months) tend to transact at higher revenue levels than Cash customers, consistent with the expectation that extended credit enables larger purchases.

  • 24-Month Warranty products: Products with 24-month warranties (higher-tier inverter systems) command higher revenue than 12-month warranty products, reflecting their premium positioning.


9 Logistic Regression — Predicting High vs Low Revenue

9.1 Model Specification

A binary outcome is created: High Revenue = 1 if the transaction revenue exceeds the median (₦4,896,000); Low Revenue = 0 otherwise. This allows a different analytical lens — rather than predicting the exact revenue level, we predict the probability that any given transaction will be a high-value one.

Code
# ── Fit logistic regression ────────────────────────────────────────────────────
logit_model <- glm(
  high_revenue ~ quantity_sold + payment_plan_months +
    sales_channel + region + warranty_month,
  data   = sales,
  family = binomial(link = "logit")
)

summary(logit_model)

Call:
glm(formula = high_revenue ~ quantity_sold + payment_plan_months + 
    sales_channel + region + warranty_month, family = binomial(link = "logit"), 
    data = sales)

Coefficients:
                              Estimate Std. Error z value Pr(>|z|)    
(Intercept)                   -2.07279    0.41308  -5.018 5.22e-07 ***
quantity_sold                  0.09019    0.01537   5.869 4.40e-09 ***
payment_plan_months            0.08562    0.01979   4.327 1.51e-05 ***
sales_channelSHOP              0.12403    0.41371   0.300   0.7643    
sales_channelE-COMMERCE        1.67883    0.51447   3.263   0.0011 ** 
sales_channelBUSINESS PARTNER -3.32153   20.75579  -0.160   0.8729    
regionAJAH                    -0.82371    0.49295  -1.671   0.0947 .  
regionEPE                     -0.24183    0.50388  -0.480   0.6313    
regionALIMOSHO                -0.14992    0.55580  -0.270   0.7874    
regionIKORODU - NORTH         -0.11778    0.50576  -0.233   0.8159    
regionEGBEDA                  -0.99287    0.75732  -1.311   0.1898    
regionBADAGRY                  0.18075    0.95879   0.189   0.8505    
warranty_month24 months       -0.99862    0.50894  -1.962   0.0497 *  
---
Signif. codes:  0 '***' 0.001 '**' 0.01 '*' 0.05 '.' 0.1 ' ' 1

(Dispersion parameter for binomial family taken to be 1)

    Null deviance: 444.92  on 320  degrees of freedom
Residual deviance: 250.08  on 308  degrees of freedom
AIC: 276.08

Number of Fisher Scoring iterations: 11
Code
# Odds ratios with confidence intervals
tidy(logit_model, exponentiate = TRUE, conf.int = TRUE) |>
  rename(Odds_Ratio = estimate) |>
  mutate(
    across(c(Odds_Ratio, std.error, conf.low, conf.high), ~ round(.x, 3)),
    p.value     = round(p.value, 4),
    Significant = case_when(
      p.value < 0.001 ~ "***",
      p.value < 0.01  ~ "**",
      p.value < 0.05  ~ "*",
      TRUE            ~ "ns"
    ),
    Interpretation = ifelse(Odds_Ratio > 1,
      paste0(round((Odds_Ratio - 1)*100, 0), "% higher odds of High Revenue"),
      paste0(round((1 - Odds_Ratio)*100, 0), "% lower odds of High Revenue"))
  ) |>
  kbl(caption = "Table 11: Logistic Regression — Odds Ratios for High Revenue Prediction") |>
  kable_styling(bootstrap_options = c("striped","hover","condensed"),
                full_width = TRUE, font_size = 11) |>
  column_spec(8, italic = TRUE)
Table 11: Logistic Regression — Odds Ratios for High Revenue Prediction
term Odds_Ratio std.error statistic p.value conf.low conf.high Significant Interpretation
(Intercept) 0.126 0.413 -5.0179423 0.0000 0.054 0.273 *** 87% lower odds of High Revenue
quantity_sold 1.094 0.015 5.8685751 0.0000 1.065 1.131 *** 9% higher odds of High Revenue
payment_plan_months 1.089 0.020 4.3270199 0.0000 1.049 1.134 *** 9% higher odds of High Revenue
sales_channelSHOP 1.132 0.414 0.2998089 0.7643 0.497 2.538 ns 13% higher odds of High Revenue
sales_channelE-COMMERCE 5.359 0.514 3.2632152 0.0011 2.009 15.341 ** 436% higher odds of High Revenue
sales_channelBUSINESS PARTNER 0.036 20.756 -0.1600292 0.8729 0.000 148.411 ns 96% lower odds of High Revenue
regionAJAH 0.439 0.493 -1.6709903 0.0947 0.161 1.126 ns 56% lower odds of High Revenue
regionEPE 0.785 0.504 -0.4799409 0.6313 0.284 2.075 ns 21% lower odds of High Revenue
regionALIMOSHO 0.861 0.556 -0.2697388 0.7874 0.286 2.556 ns 14% lower odds of High Revenue
regionIKORODU - NORTH 0.889 0.506 -0.2328853 0.8159 0.323 2.380 ns 11% lower odds of High Revenue
regionEGBEDA 0.371 0.757 -1.3110322 0.1898 0.071 1.490 ns 63% lower odds of High Revenue
regionBADAGRY 1.198 0.959 0.1885181 0.8505 0.144 7.248 ns 20% higher odds of High Revenue
warranty_month24 months 0.368 0.509 -1.9621583 0.0497 0.125 0.943 * 63% lower odds of High Revenue
Code
# Confusion matrix and accuracy
predicted_prob   <- predict(logit_model, type = "response")
predicted_class  <- factor(ifelse(predicted_prob > 0.5, "High", "Low"),
                           levels = c("Low", "High"))

conf_mat <- table(Predicted = predicted_class, Actual = sales$high_revenue)
cat("Confusion Matrix:\n")
Confusion Matrix:
Code
print(addmargins(conf_mat))
         Actual
Predicted Low High Sum
     Low  145   43 188
     High  18  115 133
     Sum  163  158 321
Code
accuracy <- sum(diag(conf_mat)) / sum(conf_mat)
cat(sprintf("\nOverall Accuracy: %.1f%%\n", accuracy * 100))

Overall Accuracy: 81.0%
Code
# AIC comparison: logit vs null model
cat(sprintf("Model AIC: %.1f | Null AIC: %.1f | Improvement: %.1f\n",
    AIC(logit_model),
    AIC(glm(high_revenue ~ 1, data = sales, family = binomial())),
    AIC(glm(high_revenue ~ 1, data = sales, family = binomial())) - AIC(logit_model)))
Model AIC: 276.1 | Null AIC: 446.9 | Improvement: 170.8

Interpretation: The logistic regression model predicts whether a transaction will be high-value with an accuracy of approximately [X]%. The strongest predictor is likely quantity_sold — transactions with higher unit volumes have disproportionately higher odds of being high-revenue. Business implication: Field agents and shop staff can use the predicted probability as a real-time indicator during the sales process — customers considering bulk purchases or 24-month warranty products should be prioritised for extended credit term offers.


10 Integrated Findings

The five analyses combine to tell one coherent story:

Revenue at Sun King Nigeria Lagos is structurally concentrated and volume-driven. EDA established that the distribution is severely right-skewed (skewness = 4.29) with a median-to-mean gap of ₦14.5 million — driven by a small number of Business Partner bulk orders. This concentration is not a data quality problem; it is a business reality that must inform every operational decision.

Visualisation revealed the geography of concentration. Ikorodu generates the most transactions (36% of total), while Business Partner channel — despite only 12 transactions (3.7%) — accounts for a disproportionate share of total Lagos revenue. November was the strongest month, suggesting year-end purchasing dynamics that could be amplified with targeted promotions.

Hypothesis testing confirmed that channel choice is not neutral. The ANOVA result (p < 0.001) establishes that sales channel has a statistically significant and practically meaningful effect on log(Revenue). This is not a coincidence of the data — it reflects genuine structural differences in who transacts through each channel and what they buy.

Correlation analysis identified the two highest-leverage variables: Quantity Sold (r = 0.82) and Sales Promotion (r = 0.64). Both are actionable — volume can be influenced by bundle pricing, and promotion spend can be increased in high-potential LGAs.

Regression quantified the relationships and identified that Sales Channel and Quantity Sold are the primary predictors of log(Revenue). The model provides a forecasting tool for target-setting: given a planned deployment of agents in a region with a specific product mix and payment plan structure, expected revenue can be estimated.

Single Integrated Recommendation: Sun King Nigeria should implement a volume-incentive programme for Agent Sales — its dominant channel by transaction count — designed to increase units-per-transaction from the current median of 5 to a target of 10, combined with active promotion of 20-month credit plans to enable this. Simultaneously, the Business Partner pipeline should be treated as a protected strategic asset: the 12 Business Partner transactions in 2025 likely generated more aggregate revenue than the bottom three LGAs combined. Dedicated account management for Business Partners would protect and grow this segment.


11 Limitations & Further Work

  1. Perfect collinearity between Revenue, Unit Price, and Quantity Sold means that Unit Price was excluded from all regression models. With access to a margin or gross profit variable, a more complete cost-of-sales model could be built that adds analytical value.

  2. The dataset is a census of 2025 transactions only. Without prior-year data, seasonal decomposition and year-on-year growth analysis are not possible. A multi-year extract would allow ARIMA-style forecasting.

  3. Business Partner transactions dominate revenue but are few in number (n=12). This creates a sample size problem for channel-specific modelling. A dedicated Business Partner transaction log with client-level attributes would substantially improve the model’s ability to explain high-revenue variance.

  4. No customer-level identifiers means customer lifetime value (CLV), repeat purchase rates, and churn analysis are not possible with this dataset — all of which would be valuable for a solar energy subscription business.

  5. Further work: With more data, Case Study 2 (Predictive Modelling & Segmentation) techniques — particularly customer clustering by LGA and product tier, and a classification model for churn risk — would be the natural next step from this exploratory analysis.


12 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

Greenlight Planet Inc. (2025). Sun King Nigeria Lagos Region sales transaction extract, January–December 2025 [Internal operational data]. Sales Operations Department, Sun King Nigeria Limited.

R Core Team. (2024). R: A language and environment for statistical computing (Version 4.x). R Foundation for Statistical Computing. https://www.R-project.org/

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

Pedersen, T. L. (2024). patchwork: The composer of plots [R package]. https://CRAN.R-project.org/package=patchwork

Wei, T., & Simko, V. (2021). corrplot: Visualisation of a correlation matrix [R package]. https://github.com/taiyun/corrplot

Firke, S. (2023). janitor: Simple tools for examining and cleaning dirty data [R package]. https://CRAN.R-project.org/package=janitor

Spinu, V., Grolemund, G., & Wickham, H. (2023). lubridate: Make dealing with dates a little easier [R package]. https://CRAN.R-project.org/package=lubridate

Ben-Shachar, M. S., Lüdecke, D., & Makowski, D. (2020). effectsize: Estimation of effect size indices and standardised parameters. Journal of Open Source Software, 5(56), 2815. https://doi.org/10.21105/joss.02815


13 Appendix: AI Usage Statement

AI coding assistants (Claude by Anthropic) were used in the preparation of this document to assist with: generating initial R code structure for data cleaning and visualisation, suggesting appropriate statistical tests given the dataset characteristics, and formatting the Quarto document YAML header. All analytical decisions — including the choice of log transformation, the selection of the ANOVA and chi-squared tests as the two hypothesis tests, the exclusion of Unit Price from regression due to collinearity, and all plain-language interpretations of statistical outputs — were made independently based on my understanding of the data, the course textbook (Adi, 2026), and the business context of Sun King Nigeria’s operations. Every line of code was reviewed, modified where necessary, and tested against the actual dataset before inclusion. All statistical interpretations and business recommendations in this document represent my own independent analytical judgement.