Introduction

The dataset used in this project is from Kaggle, published by the user The Devastator. The dataset comprises sales records of a multinational company, documenting transactions associated with bicycles across four countries. This report delves into a comprehensive dataset comprising 34,866 observations across 17 variables. The aim is to extract actionable insights to inform strategic business decisions. By analyzing customer demographics, product details, and financial metrics, we’ll uncover patterns and trends that can shape marketing strategies. This data-driven exploration is geared towards enhancing decision-making and driving business success.

One limitation of the dataset is the absence of currency information for transactions, necessitating the retention of the numerical values as originally provided.It is also not known if the data is real or simulated.

The core objectives of the project include:

-Comprehending customer behavior and their product interactions

-Appraising the performance of individual products

-Analyzing the company’s revenue performance

Load and Inspect Data

setwd("C:/Users/rober/Downloads/DataVizProject")
data <- read.csv("customer_purchases.csv")
head(data)
##   index     Date Year    Month Customer.Age Customer.Gender       Country
## 1     0 02/19/16 2016 February           29               F United States
## 2     1 02/20/16 2016 February           29               F United States
## 3     2 02/27/16 2016 February           29               F United States
## 4     3 03/12/16 2016    March           29               F United States
## 5     4 03/12/16 2016    March           29               F United States
## 6     5 04/08/16 2016    April           29               F United States
##        State Product.Category    Sub.Category Quantity Unit.Cost Unit.Price
## 1 Washington      Accessories Tires and Tubes        1     80.00  109.00000
## 2 Washington         Clothing          Gloves        2     24.50   28.50000
## 3 Washington      Accessories Tires and Tubes        3      3.67    5.00000
## 4 Washington      Accessories Tires and Tubes        2     87.50  116.50000
## 5 Washington      Accessories Tires and Tubes        3     35.00   41.66667
## 6 Washington      Accessories Tires and Tubes        1     66.00   78.00000
##   Cost Revenue Column1
## 1   80     109      NA
## 2   49      57      NA
## 3   11      15      NA
## 4  175     233      NA
## 5  105     125      NA
## 6   66      78      NA
str(data)
## 'data.frame':    34867 obs. of  16 variables:
##  $ index           : int  0 1 2 3 4 5 6 7 8 9 ...
##  $ Date            : chr  "02/19/16" "02/20/16" "02/27/16" "03/12/16" ...
##  $ Year            : int  2016 2016 2016 2016 2016 2016 2016 2016 2016 2016 ...
##  $ Month           : chr  "February" "February" "February" "March" ...
##  $ Customer.Age    : int  29 29 29 29 29 29 29 29 29 29 ...
##  $ Customer.Gender : chr  "F" "F" "F" "F" ...
##  $ Country         : chr  "United States" "United States" "United States" "United States" ...
##  $ State           : chr  "Washington" "Washington" "Washington" "Washington" ...
##  $ Product.Category: chr  "Accessories" "Clothing" "Accessories" "Accessories" ...
##  $ Sub.Category    : chr  "Tires and Tubes" "Gloves" "Tires and Tubes" "Tires and Tubes" ...
##  $ Quantity        : int  1 2 3 2 3 1 2 1 2 2 ...
##  $ Unit.Cost       : num  80 24.5 3.67 87.5 35 66 52 60 8 2.5 ...
##  $ Unit.Price      : num  109 28.5 5 116.5 41.7 ...
##  $ Cost            : int  80 49 11 175 105 66 104 60 16 5 ...
##  $ Revenue         : num  109 57 15 233 125 78 120 68 20 6 ...
##  $ Column1         : num  NA NA NA NA NA NA NA NA NA NA ...
unique_countries <- unique(data$Country)
print(unique_countries)
## [1] "United States"  "France"         "United Kingdom" "Germany"       
## [5] ""
unique_products <-unique(data$Product.Category)
print(unique_products)
## [1] "Accessories" "Clothing"    "Bikes"       ""
unique_subCat <-unique(data$Sub.Category)
print(unique_subCat)
##  [1] "Tires and Tubes"   "Gloves"            "Helmets"          
##  [4] "Bike Stands"       "Mountain Bikes"    "Hydration Packs"  
##  [7] "Jerseys"           "Fenders"           "Cleaners"         
## [10] "Socks"             "Caps"              "Touring Bikes"    
## [13] "Bottles and Cages" "Vests"             "Road Bikes"       
## [16] "Bike Racks"        "Shorts"            ""

Data Cleaning

“Column1” contains null values and should be excluded from the analysis. Similarly, the “index” column lacks meaningful information and can be safely removed.
The “Date” column is currently in “chr” data type; it should be converted to the “Date” data type for accurate data handling. Also, we can round the figures in the Unit Price column to cut off trailing zeros.

data <- subset(data, select = -Column1) 
data <- subset(data, select = -index) 
data$Date <- as.Date(data$Date, format = "%m/%d/%y")
data$Unit.Price <- round(data$Unit.Price, 2)

Check to see if any rows contain null values

if (any(is.na(data))) {
  print("There are NA values in the data frame.")
} else {
  print("There are no NA values in the data frame.")
}
## [1] "There are NA values in the data frame."
rows_with_na <- rownames(data)[apply(data, 1, function(x) any(is.na(x)))]
print(rows_with_na)
## [1] "34867"
data <- data[-34867,] #remove row with null value

Create new columns that would help us gain insights into business metrics

data$margin <- data$Unit.Price - data$Unit.Cost 
data$Profit <- data$Revenue - data$Cost 
install.packages("tidyverse", repos = "http://cran.us.r-project.org")
## Installing package into 'C:/Users/rober/AppData/Local/R/win-library/4.2'
## (as 'lib' is unspecified)
## package 'tidyverse' successfully unpacked and MD5 sums checked
## 
## The downloaded binary packages are in
##  C:\Users\rober\AppData\Local\Temp\RtmpSG065W\downloaded_packages
library("tidyverse")
## Warning: package 'tidyverse' was built under R version 4.2.3
## Warning: package 'ggplot2' was built under R version 4.2.3
## Warning: package 'tibble' was built under R version 4.2.3
## Warning: package 'tidyr' was built under R version 4.2.3
## Warning: package 'readr' was built under R version 4.2.3
## Warning: package 'purrr' was built under R version 4.2.3
## Warning: package 'dplyr' was built under R version 4.2.3
## Warning: package 'forcats' was built under R version 4.2.3
## Warning: package 'lubridate' was built under R version 4.2.3
## ── Attaching core tidyverse packages ──────────────────────── tidyverse 2.0.0 ──
## ✔ dplyr     1.1.2     ✔ readr     2.1.4
## ✔ forcats   1.0.0     ✔ stringr   1.5.0
## ✔ ggplot2   3.4.2     ✔ tibble    3.2.1
## ✔ lubridate 1.9.2     ✔ tidyr     1.3.0
## ✔ purrr     1.0.1
## ── Conflicts ────────────────────────────────────────── tidyverse_conflicts() ──
## ✖ dplyr::filter() masks stats::filter()
## ✖ dplyr::lag()    masks stats::lag()
## ℹ Use the ]8;;http://conflicted.r-lib.org/conflicted package]8;; to force all conflicts to become errors

Data Exploration: Customer Demographics

gender_table <- table(data$Customer.Gender)
pct_gender<- round((gender_table / sum(gender_table)) * 100, 2)
gendercount <- paste(names(gender_table), ": ", gender_table, " (", pct_gender, "%)", sep="")

pie(gender_table, labels = gendercount, main = "Gender Distribution")

data <- data %>%
  mutate(Age.Group = case_when( #form age groups from Customer.Age                                                                 column
    Customer.Age >= 17 & Customer.Age <= 24 ~ '17-24',
    Customer.Age >= 25 & Customer.Age <= 34 ~ '25-34',
    Customer.Age >= 35 & Customer.Age <= 44 ~ '35-44',
    Customer.Age >= 45 & Customer.Age <= 54 ~ '45-54',
    Customer.Age >= 55 & Customer.Age <= 64 ~ '55-64',
    Customer.Age >= 65 & Customer.Age <= 74 ~ '65-74',
    Customer.Age >= 75 ~ '75+'
  ))

ggplot(data, aes(x = Age.Group)) +
  geom_bar(fill = "green") +
  labs(x = "Age Group", y = "Count", title = "Age Distribution") +
  theme_minimal()

Composition of Sales

#bar chart product category
ggplot(data, aes(x = Product.Category)) +
  geom_bar(fill = "blue") +
  labs(title = "Product Category Distribution", x = "Product Category", y = "Count")

#bar chart product subcategory
ggplot(data, aes(x = Sub.Category)) +
  geom_bar(fill = "blue") +
  labs(title = "Product Subcategory Distribution", x = "Product Category", y = "Count") +
  theme(axis.text.x = element_text(angle = 45, hjust = 1))

#stacked bar chart
ggplot(data, aes(x = Product.Category, fill = Sub.Category)) +
  geom_bar() +
  labs(x = "Product Category", y = "Count", fill = "Sub-Category") +
  theme_minimal()

These visualizations reinforce the fact that accessories is the most popular product category. From the stacked bar chart, we learn that the product subcategory tires and tubes is the most popular, while bike racks is the least popular subcategory.

Lets examine the profitability of the product subcategories

sub_category_summary <- data %>%
  group_by(Sub.Category) %>%
  summarise(Avg_Unit_Price = mean(Unit.Price),
            Avg_Unit_Cost = mean(Unit.Cost),
            Avg_Margins = mean(margin))

ggplot(sub_category_summary, aes(x = Sub.Category)) +
  geom_point(aes(y = Avg_Unit_Price, color = "Average Unit Price")) +
  geom_point(aes(y = Avg_Unit_Cost, color = "Average Unit Cost")) +
  geom_point(aes(y = Avg_Margins, color = "Average Margin")) +
  labs(title = "Average Unit Price, Cost, Margins by Sub-Category",
       x = "Sub-Category",
       y = "Average Unit Price / Cost") +
  scale_color_manual(values = c("Average Unit Price" = "blue", "Average Unit Cost" = "red", "Average Margin" = "green")) +
  theme_minimal() +
  theme(axis.text.x = element_text(angle = 45, hjust = 1))

#Profitability by sub-category
ggplot(data, aes(x = Sub.Category, y = margin, fill = Product.Category)) +
  geom_bar(stat = "summary", fun = "mean") +
  labs(x = "Sub Category", y = "Margins") +
  labs(title = "Mean Margins by sub-category",
       x = "Country",
       y = "Mean Margin") +
  theme_minimal()+
  theme(axis.text.x = element_text(angle = 45, hjust = 1)) 

Metrics by Country

#US SALES
data_us <- data[data$Country == "United States", ]

category_US <- table(data_us$Product.Category)

category_percentages <- round(100 * category_US / sum(category_US), 1)
labels <- paste(names(category_US), "\n", category_US, " (", category_percentages, "%)", sep = "")

pie(category_US, labels = labels,
    main = "Distribution of Product Categories in the United States")

#GERMANY SALES
germany <- data[data$Country == "Germany", ]
category_Germany <- table(germany$Product.Category)

category_percentages <- round(100 * category_Germany / sum(category_Germany), 1)
labels <- paste(names(category_Germany), "\n", category_Germany, " (", category_percentages, "%)", sep = "")

pie(category_Germany, labels = labels,
    main = "Distribution of Product Categories in Germany")

#FRANCE SALES
France <- data[data$Country == "France", ]
category_France <- table(France$Product.Category)

category_percentages <- round(100 * category_France / sum(category_France), 1)
labels <- paste(names(category_France), "\n", category_France, " (", category_percentages, "%)", sep = "")

pie(category_France, labels = labels,
    main = "Distribution of Product Categories in France")

#UK SALES
UK <- data[data$Country == "United Kingdom", ]
category_UK <- table(UK$Product.Category)

category_percentages <- round(100 * category_UK / sum(category_UK), 1)
labels <- paste(names(category_UK), "\n", category_UK, " (", category_percentages, "%)", sep = "")

pie(category_UK, labels = labels,
    main = "Distribution of Product Categories in the United Kingdom")

In the four countries under consideration, the sales composition remains fairly consistent, with accessories accounting for approximately two-thirds of total sales. Bikes constitute roughly 20% of the sales, while clothing comprises approximately 15%.

Now lets examine each country’s business metrics: revenue, cost, profit, and margins.

#revenue, profit, cost by country
country_metrics <- data %>%
  group_by(Country) %>%
  summarise(total_Profit = sum(Profit),
            total_Cost = sum(Cost),
            total_Revenue = sum(Revenue),
            avg_margins = mean(margin))
#bar graph for metrics
ggplot(country_metrics, aes(x = Country)) +
  geom_bar(aes(y = total_Profit, fill = "Total Profit"), stat = "identity", position = "dodge") +
  geom_bar(aes(y = total_Cost, fill = "Total Cost"), stat = "identity", position = "dodge", width = 0.5) +
  geom_bar(aes(y = total_Revenue, fill = "Total Revenue"), stat = "identity", position = "dodge", width = 0.25) +
  labs(title = "Business Metrics by Country",
       x = "Country",
       y = "Amount") +
  scale_fill_manual(values = c("Total Profit" = "blue", "Total Cost" = "red", "Total Revenue" = "green")) +
  theme_minimal() +
  theme(axis.text.x = element_text(angle = 45, hjust = 1)) +
  scale_y_continuous(labels = scales::comma)

Despite having the most customers and greatest total revenue, the United States does not generate the most profit. Let’s take a look at some factors that could be the root of this occurence: unit price and margins

#mean unit price by country
ggplot(data, aes(x = Country, y = Unit.Price)) +
  geom_bar(stat = "summary", fun = "mean", fill = "blue") +
  labs(title = "Relationship between (mean) Unit Price and Country",
       x = "Country",
       y = "Mean Unit Price") +
  theme_minimal()

#mean margin by country
ggplot(data, aes(x = Country, y = margin)) +
  geom_bar(stat = "summary", fun = "mean", fill = "blue") +
  labs(title = "Relationship between (mean) Margin and Country",
       x = "Country",
       y = "Mean Margin") +
  theme_minimal()

Again, What’s noteworthy is that despite the United States having the largest market generating the highest total revenue at 10,377,742, more than double that of the second-ranking country, the United Kingdom with 4,276,220, it does not boast the highest profit. In fact, Germany emerges as the leader in total profit, a fact that can be attributed to its considerably superior profit margins and higher unit prices compared to those of the United States. Germany’s mean margin stands at 123, while the United States lags behind with a mean margin of 23.

Conclusion

The market data was divided by country to represent different markets. In terms of market segmentation, the United States boasts the highest customer count, while Germany and France lag behind with the fewest customers. Despite the United States having the largest market share in population terms, it surprisingly generated the least profit, with Germany leading in average profit margins.

Analyzing the situation, the variations in revenue and prices can be attributed to several factors:

-The company’s strategy of attracting a large customer base in the United States through low prices or discounts.

-The most popular products in the United States have lower profit margins compared to those in Germany.

In 2016, the company experienced a substantial decline in revenue from June to July. While the causes are unknown, the company can analyze market trends, customer feedback, and internal operations to identify contributing factors.