---
title: "Smart Warehouse Inventory Simulation"
subtitle: "Data Manipulation"
author: "Sahyra Khan | Student ID: 2237641"
date: "23 October 2026"
output:
  word_document:
    toc: true
  html_document:
    toc: true
    number_sections: true
  pdf_document:
    toc: true
---

# Introduction

This R Markdown document explores the creation and analysis of simulated data from a smart warehouse environment. The dataset represents 28 days of warehouse activity across five zones and six product categories, providing a structured example of how inventory-processing data can be organised and analysed.
The data is generated and analysed using nested loops, allowing each stage of the process to be carried out clearly and systematically. To follow the assessment requirements, high-level aggregation functions and user-defined functions have not been used.

The analysis covers:

- Generation of the warehouse dataset
- Total units processed by warehouse zone
- Average units processed per zone across 28 days
- Average processing time by product category
- Detection of processing anomalies
- Identification of the most productive day for each warehouse zone

# Setup

The following values are used to create the simulated warehouse dataset.

```{r}
# Set seed so that the random results can be reproduced
set.seed(2237641)

# Define warehouse constants
number_of_days <- 28
number_of_zones <- 5
number_of_categories <- 6

base_units <- 100
time_per_unit <- 0.85
```

# Task A: Generate Warehouse Data

The first task creates the `warehouse_data` dataframe using nested loops. The dataset contains every combination of 28 days, five warehouse zones and six product categories.

This produces:

**28 × 5 × 6 = 840 observations.**

For each record, `UnitsProcessed` is calculated as:

**UnitsProcessed = 100 + (Zone × 8) + (Category × 5) + RandomVariation**

where `RandomVariation` is a random integer between -12 and +12.

Processing time is calculated as:

**ProcessingTime = UnitsProcessed × 0.85**


```{r}
# Create an empty dataframe
warehouse_data <- data.frame(
  Day = integer(),
  Zone = integer(),
  Category = integer(),
  UnitsProcessed = integer(),
  ProcessingTime = numeric()
)

# Start the row counter
row_number <- 1

# Generate warehouse data using nested loops
for (day in 1:number_of_days) {
  
  for (zone in 1:number_of_zones) {
    
    for (category in 1:number_of_categories) {
      
      # Generate random variation between -12 and +12
      random_variation <- sample(-12:12, 1)
      
      # Calculate units processed
      units_processed <- base_units +
        (zone * 8) +
        (category * 5) +
        random_variation
      
      # Calculate processing time
      processing_time <- units_processed * time_per_unit
      
      # Add the record to the dataframe
      warehouse_data[row_number, ] <- c(
        day,
        zone,
        category,
        units_processed,
        processing_time
      )
      
      # Move to the next row
      row_number <- row_number + 1
    }
  }
}

# Round processing time to two decimal places
warehouse_data$ProcessingTime <- round(
  warehouse_data$ProcessingTime,
  2
)

# Display the first 10 rows
head(warehouse_data, 10)

# Display the number of rows and columns
cat("Total number of rows:", nrow(warehouse_data), "\n")
cat("Total number of columns:", ncol(warehouse_data), "\n")
```

## Task A Summary

The simulation successfully generated 840 warehouse records covering 28 days, five zones and six product categories. Each record contains the units processed and its corresponding processing time, providing the main dataset for the remaining analysis.

# Task B: Total Units Processed per Zone

This task calculates the total number of units processed by each warehouse zone across all 28 days and six product categories.

A loop is used to examine each of the five warehouse zones. For each zone, a second loop checks every row of the `warehouse_data` dataframe. When a record belongs to the current zone, its `UnitsProcessed` value is added to a running total.

The calculated totals are stored in a new dataframe called `zone_totals`.



```{r}
# ------------------------------------------------------------
# TASK B: TOTAL UNITS PROCESSED PER ZONE
# ------------------------------------------------------------

# Create an empty dataframe to store the results
zone_totals <- data.frame(
  Zone = integer(),
  TotalUnits = integer()
)

# Loop through each warehouse zone
for (zone in 1:number_of_zones) {
  
  # Start the total at zero for the current zone
  total_units <- 0
  
  # Loop through every row in warehouse_data
  for (i in 1:nrow(warehouse_data)) {
    
    # Check if the current row belongs to this zone
    if (warehouse_data$Zone[i] == zone) {
      
      # Add the units processed to the running total
      total_units <- total_units +
        warehouse_data$UnitsProcessed[i]
    }
  }
  
  # Store the zone number and calculated total
  zone_totals[zone, ] <- c(
    zone,
    total_units
  )
}

# Display the completed dataframe
zone_totals
```


## Task B Summary

The total units processed were calculated separately for each of the five warehouse zones using loops. The results show the overall processing activity of each zone across all 28 days and product categories.

# Additional Analysis: Average Units per Zone Across 28 Days

This additional analysis calculates the average units processed for each warehouse zone across the full 28-day simulation. Nested loops accumulate the units processed and count all matching records across the six product categories. The total is divided by the record count to produce one average for each zone.

```{r}
# ------------------------------------------------------------
# ADDITIONAL ANALYSIS: AVERAGE UNITS PER ZONE ACROSS 28 DAYS
# ------------------------------------------------------------

# Create an empty dataframe
zone_averages <- data.frame(
  Zone = integer(),
  AverageUnits = numeric()
)

# Loop through all five zones
for (zone in 1:number_of_zones) {

  # Start the total and record count at zero
  total_units <- 0
  record_count <- 0

  # Check all warehouse records
  for (i in 1:nrow(warehouse_data)) {

    if (warehouse_data$Zone[i] == zone) {

      # Add the units processed
      total_units <- total_units +
        warehouse_data$UnitsProcessed[i]

      # Count each matching record
      record_count <- record_count + 1
    }
  }

  # Calculate average across all days and categories
  average_units <- total_units / record_count

  # Store the result
  zone_averages[zone, ] <- c(
    zone,
    round(average_units, 2)
  )
}

# Display the five zone averages
zone_averages
```


# Task C: Average Processing Time per Product Category

This task calculates the average processing time for each of the six product categories across all warehouse zones and days.

For each product category, a loop examines every row of the `warehouse_data` dataframe. The total processing time and the number of matching records are calculated separately. The average processing time is then calculated by dividing the total processing time by the number of matching records.

The results are stored in a new dataframe called `category_average_time`.


```{r}
# ------------------------------------------------------------
# TASK C: Average processing time per product category
# ------------------------------------------------------------

# Create an empty dataframe to store the results
category_average_time <- data.frame(
  Category = integer(),
  AverageProcessingTime = numeric()
)
 # Loop through each product category
for (category in 1:number_of_categories) {
  
  # Start the total processing time and record count at zero
  total_processing_time <- 0
  record_count <- 0
  
  # Loop through every row in warehouse_data
  for (i in 1:nrow(warehouse_data)) {
    
    # Check if the current row belongs to this category
    if (warehouse_data$Category[i] == category) {
      
      # Add processing time to the running total
      total_processing_time <- total_processing_time +
        warehouse_data$ProcessingTime[i]
      
      # Count the matching record
      record_count <- record_count + 1
    }
  }
  
  # Calculate the average processing time
  average_processing_time <- total_processing_time / record_count
  
  # Store the category and its average processing time
  category_average_time[category, ] <- c(
    category,
    round(average_processing_time, 2)
  )
}

# Display the completed dataframe
category_average_time
```

## Task C Summary

The average processing time was calculated for each of the six product categories. Loops were used to calculate both the total processing time and the number of matching records before determining each category average.


# Task D: Identify Stock Processing Anomalies

This task identifies unusual levels of warehouse processing. For each day and warehouse zone, the average number of units processed across the six product categories is calculated.

Nested loops are used to examine each day and zone combination. The total units processed and number of matching records are first calculated to obtain the `DailyZoneAverage`.

Each record is then compared with its corresponding daily zone average. A record is classified as an anomaly when its `UnitsProcessed` value is more than 15% above or more than 15% below the daily zone average.


The anomaly conditions are:

**UnitsProcessed > DailyZoneAverage × 1.15**

or

**UnitsProcessed < DailyZoneAverage × 0.85**

The results are stored in the `processing_anomalies` dataframe.


```{r}
# ------------------------------------------------------------
# TASK D: IDENTIFY STOCK PROCESSING ANOMALIES
# ------------------------------------------------------------

# Create an empty dataframe to store the results
processing_anomalies <- data.frame(
  Day = integer(),
  Zone = integer(),
  Category = integer(),
  UnitsProcessed = integer(),
  DailyZoneAverage = numeric(),
  Anomaly = logical()
)

# Start the row counter
anomaly_row <- 1

# Loop through each day
for (day in 1:number_of_days) {
  
  # Loop through each warehouse zone
  for (zone in 1:number_of_zones) {
    
    # Start totals at zero for the current day and zone
    total_daily_zone_units <- 0
    daily_zone_count <- 0
    
    # Find all records belonging to this day and zone
    for (i in 1:nrow(warehouse_data)) {
      
      if (warehouse_data$Day[i] == day &&
          warehouse_data$Zone[i] == zone) {
        
        # Add units processed to the running total
        total_daily_zone_units <- total_daily_zone_units +
          warehouse_data$UnitsProcessed[i]
        
        # Count the matching record
        daily_zone_count <- daily_zone_count + 1
      }
    }
    
    # Calculate the average for this day and zone
    daily_zone_average <- total_daily_zone_units /
      daily_zone_count
    
    # Check each record for an anomaly
    for (i in 1:nrow(warehouse_data)) {
      
      if (warehouse_data$Day[i] == day &&
          warehouse_data$Zone[i] == zone) {
        
        # Get units processed for the current record
        current_units <- warehouse_data$UnitsProcessed[i]
        
        # Check whether the value is outside the 15% limits
        is_anomaly <- current_units >
          (daily_zone_average * 1.15) ||
          current_units <
          (daily_zone_average * 0.85)
        
        # Store the result
        processing_anomalies[anomaly_row, ] <- c(
          day,
          zone,
          warehouse_data$Category[i],
          current_units,
          round(daily_zone_average, 2),
          is_anomaly
        )
        
        # Move to the next row
        anomaly_row <- anomaly_row + 1
      }
    }
  }
}

# Convert the Anomaly column to logical TRUE/FALSE values
processing_anomalies$Anomaly <- as.logical(
  processing_anomalies$Anomaly
)

# Create an empty dataframe for TRUE anomalies only
true_anomalies <- processing_anomalies[0, ]

# Start a new row counter
true_anomaly_row <- 1

# Find records where Anomaly is TRUE
for (i in 1:nrow(processing_anomalies)) {
  
  if (processing_anomalies$Anomaly[i] == TRUE) {
    
    true_anomalies[true_anomaly_row, ] <-
      processing_anomalies[i, ]
    
    true_anomaly_row <- true_anomaly_row + 1
  }
}

# Display the first 10 TRUE anomalies
head(true_anomalies, 10)
```

## Task D Summary

Processing anomalies were identified by comparing each record with the average units processed for its corresponding day and zone. Records more than 15% above or below the daily zone average were classified as anomalies, allowing unusually high or low processing activity to be identified.


# Task E: Identify the Most Productive Day for Each Zone

This task identifies the day with the highest total number of units processed for each warehouse zone.

For each zone, a loop examines all 28 days. The total number of units processed across the six product categories is calculated for each day using another loop.

The daily total is compared with the highest total found so far. If the current day's total is higher, the day and total are stored as the new peak values.

The results are stored in the `peak_zone_days` dataframe.



```{r}
# ------------------------------------------------------------
# TASK E: MOST PRODUCTIVE DAY FOR EACH ZONE
# ------------------------------------------------------------

# Create an empty dataframe to store the results
peak_zone_days <- data.frame(
  Zone = integer(),
  Day = integer(),
  TotalUnits = integer()
)

# Loop through each warehouse zone
for (zone in 1:number_of_zones) {
  
  # Set starting values for the highest total and day
  highest_total <- -Inf
  highest_day <- NA
  
  # Loop through all 28 days
  for (day in 1:number_of_days) {
    
    # Start the daily total at zero
    daily_total <- 0
    
    # Loop through every row in warehouse_data
    for (i in 1:nrow(warehouse_data)) {
      
      # Check if the row belongs to the current zone and day
      if (warehouse_data$Zone[i] == zone &&
          warehouse_data$Day[i] == day) {
        
        # Add units processed to the daily total
        daily_total <- daily_total +
          warehouse_data$UnitsProcessed[i]
      }
    }
    
    # Check if this is the highest daily total found so far
    if (daily_total > highest_total) {
      
      highest_total <- daily_total
      highest_day <- day
    }
  }
  
  # Store the most productive day for the current zone
  peak_zone_days[zone, ] <- c(
    zone,
    highest_day,
    highest_total
  )
}

# Display the completed dataframe
peak_zone_days
```

## Task E Summary

The most productive day was identified for each of the five warehouse zones by comparing the total units processed across all 28 days. The day with the highest total processing activity was recorded for each zone in the `peak_zone_days` dataframe.


# Code Presentation

The R code has been organised into clearly labelled sections for each task, with consistent indentation, spacing and descriptive comments. Meaningful variable and dataframe names have been used throughout to improve readability and make the purpose of each calculation clear.

The solution uses nested loops for the main data manipulation tasks and follows the programming restrictions specified in the assessment brief.


# Overall Conclusion 

The Smart Warehouse Inventory Simulation successfully generated and analysed warehouse processing data across 28 days, 5 zones and 6 product categories. Loop-based programming was used throughout the main tasks to generate the dataset, calculate zone totals and category averages, identify processing anomalies, and determine the most productive day for each zone.

The analysis demonstrates how R can be used to manipulate, examine and summarise simulated operational data while following the programming restrictions specified in the assessment. The resulting dataframes provide useful information about warehouse productivity, processing performance and unusual activity.




