Loading packages and libraries

library(tidyverse)
## ── Attaching core tidyverse packages ──────────────────────── tidyverse 2.0.0 ──
## ✔ dplyr     1.2.0     ✔ readr     2.1.6
## ✔ forcats   1.0.1     ✔ stringr   1.6.0
## ✔ ggplot2   4.0.2     ✔ tibble    3.3.1
## ✔ lubridate 1.9.5     ✔ tidyr     1.3.2
## ✔ purrr     1.2.1     
## ── Conflicts ────────────────────────────────────────── tidyverse_conflicts() ──
## ✖ dplyr::filter() masks stats::filter()
## ✖ dplyr::lag()    masks stats::lag()
## ℹ Use the conflicted package (<http://conflicted.r-lib.org/>) to force all conflicts to become errors
library(readr)
library(conflicted) #loads conflicted package to provide conflicted resolution strategy to tidyverse package
library(dplyr)
library(tibble) #For tibble formatting
library(lubridate) #For date formatting
library(clock)
library(stringr)
library(tidyr)
library(ggplot2)

Importing datasets, assing variables to each dataset and convert them to tibbles for easier read.

#Import PRC Dataset and assign to variable


PRC2024 <- read.csv("Data/Raw/PRC2024.csv")

Summary of PRC2024 dataset

summary(PRC2024)
##      Index       Name.of.Entity     Likely.Duplicate.Breach    Source         
##  Min.   :    1   Length:35167       Length:35167            Length:35167      
##  1st Qu.: 8792   Class :character   Class :character        Class :character  
##  Median :17584   Mode  :character   Mode  :character        Mode  :character  
##  Mean   :17584                                                                
##  3rd Qu.:26376                                                                
##  Max.   :35167                                                                
##                                                                               
##  Reported.Date.Month Reported.Date.Day Reported.Date.Year Date.of.Breach.Month
##  Length:35167        Min.   : 1.00     Min.   :  37       Length:35167        
##  Class :character    1st Qu.: 8.00     1st Qu.:2016       Class :character    
##  Mode  :character    Median :15.00     Median :2018       Mode  :character    
##                      Mean   :15.57     Mean   :2018                           
##                      3rd Qu.:23.00     3rd Qu.:2020                           
##                      Max.   :31.00     Max.   :2023                           
##                      NA's   :621       NA's   :2488                           
##  Date.of.Breach.Day Date.of.Breach.Year Organization.Type 
##  Min.   : 0.00      Min.   :   0        Length:35167      
##  1st Qu.: 6.00      1st Qu.:2016        Class :character  
##  Median :13.00      Median :2018        Mode  :character  
##  Mean   :14.32      Mean   :2018                          
##  3rd Qu.:22.00      3rd Qu.:2020                          
##  Max.   :31.00      Max.   :2023                          
##  NA's   :8015       NA's   :9842                          
##  Organization.Type.Explanation Breach.Type        Breach.Impute     
##  Length:35167                  Length:35167       Length:35167      
##  Class :character              Class :character   Class :character  
##  Mode  :character              Mode  :character   Mode  :character  
##                                                                     
##                                                                     
##                                                                     
##                                                                     
##  Breach.Type.Explanation Max.Records.Impacted Max.Records.Impacted.Explanation
##  Length:35167            Length:35167         Length:35167                    
##  Class :character        Class :character     Class :character                
##  Mode  :character        Mode  :character     Mode  :character                
##                                                                               
##                                                                               
##                                                                               
##                                                                               
##  Description        Information.Types  Encryption.Status 
##  Length:35167       Length:35167       Length:35167      
##  Class :character   Class :character   Class :character  
##  Mode  :character   Mode  :character   Mode  :character  
##                                                          
##                                                          
##                                                          
##                                                          
##  Information.Types.Description breach_location_street breach_location_city
##  Length:35167                  Length:35167           Length:35167        
##  Class :character              Class :character       Class :character    
##  Mode  :character              Mode  :character       Mode  :character    
##                                                                           
##                                                                           
##                                                                           
##                                                                           
##  breach_location_state breach_location_ZIP breach_location_country
##  Length:35167          Length:35167        Length:35167           
##  Class :character      Class :character    Class :character       
##  Mode  :character      Mode  :character    Mode  :character       
##                                                                   
##                                                                   
##                                                                   
##                                                                   
##  breach_location_explanation Records.Impacted.From.Source
##  Length:35167                Length:35167                
##  Class :character            Class :character            
##  Mode  :character            Mode  :character            
##                                                          
##                                                          
##                                                          
##                                                          
##  State.Records.Impacted.from.Source Notification.URL       PDF           
##  Length:35167                       Length:35167       Length:35167      
##  Class :character                   Class :character   Class :character  
##  Mode  :character                   Mode  :character   Mode  :character  
##                                                                          
##                                                                          
##                                                                          
##                                                                          
##    DBA.UUID         Date.of.Breach.End.Month Date.of.Breach.End.Day
##  Length:35167       Length:35167             Min.   : 1.00         
##  Class :character   Class :character         1st Qu.: 8.00         
##  Mode  :character   Mode  :character         Median :15.00         
##                                              Mean   :15.26         
##                                              3rd Qu.:23.00         
##                                              Max.   :31.00         
##                                              NA's   :23937         
##  Date.of.Breach.End.Year Total.Records.Impacted.From.Letter
##  Min.   :  31            Length:35167                      
##  1st Qu.:2016            Class :character                  
##  Median :2018            Mode  :character                  
##  Mean   :2017                                              
##  3rd Qu.:2019                                              
##  Max.   :2023                                              
##  NA's   :24588                                             
##  State.Records.Impacted.from.Letter Description.of.Breach
##  Length:35167                       Length:35167         
##  Class :character                   Class :character     
##  Mode  :character                   Mode  :character     
##                                                          
##                                                          
##                                                          
## 
str(PRC2024)
## 'data.frame':    35167 obs. of  38 variables:
##  $ Index                             : int  1 2 3 4 5 6 7 8 9 10 ...
##  $ Name.of.Entity                    : chr  "STRATA Trust Company" "Gannett Company Inc." "Somerset County" "Bed Bath & Beyond Inc." ...
##  $ Likely.Duplicate.Breach           : chr  "" "Gannett_Company_Inc.-6fc8ae0a-89e2-4dbd-8f05-cce0458c40f2" "" "Bed_Bath_&_Beyond_Inc.-0bf67bf4-6042-4d0c-ae4d-52a8010cc0b6" ...
##  $ Source                            : chr  "CA" "IA" "IN" "ME" ...
##  $ Reported.Date.Month               : chr  "7" "4" "9" "2" ...
##  $ Reported.Date.Day                 : int  17 28 28 13 2 4 18 18 5 1 ...
##  $ Reported.Date.Year                : int  2020 2017 2022 2018 2023 2017 2017 2013 2018 2022 ...
##  $ Date.of.Breach.Month              : chr  "2" "3" "5" "11" ...
##  $ Date.of.Breach.Day                : int  10 30 19 21 5 16 19 10 5 1 ...
##  $ Date.of.Breach.Year               : int  2020 2017 2022 2017 2023 2017 2016 2013 2018 2021 ...
##  $ Organization.Type                 : chr  "BSF" "BSO" "GOV" "BSR" ...
##  $ Organization.Type.Explanation     : chr  "STRATA Trust Company is a trust company, which suggests it manages investments, financial assets, and other services typically  "Gannett Company, Inc. is primarily a media company involved in publishing industries, making it a BSO type of organization." "The name 'Somerset County' indicates it is a local government entity, as counties are typically administrative divisions within "Bed Bath & Beyond Inc. is a retail business, which falls into the category of Retail Businesses (BSR)." ...
##  $ Breach.Type                       : chr  "HACK" "HACK" "UNKN" "UNKN" ...
##  $ Breach.Impute                     : chr  "HACK" "HACK" "INSD" "CARD" ...
##  $ Breach.Type.Explanation           : chr  "The description of the breach indicates that there was 'unusual activity involving an employee email account in"| __truncated__ "The breach was a result of a phishing attack targeting HR department members which led to the compromise of Off"| __truncated__ "The breach type is classified as 'UNKN' due to the lack of specific information given about the nature of the b"| __truncated__ "Insufficient details about the breach are provided. It is unclear whether it was a hacking incident, an interna"| __truncated__ ...
##  $ Max.Records.Impacted              : chr  "UNKN" "18100" "14599" "1" ...
##  $ Max.Records.Impacted.Explanation  : chr  "There is no numerical information provided in either the source or the letter regarding the total or state reco"| __truncated__ "The data breach notification letter states that approximately 18,100 individuals were affected by this incident"| __truncated__ "The maximum number of records impacted is determined to be 14,599, which is the total provided from the source."| __truncated__ "Although the total records impacted are not provided, the source states that there was 1 state record impacted."| __truncated__ ...
##  $ Description                       : chr  "" "" "" "" ...
##  $ Information.Types                 : chr  "[{type: UNKN" "[{type: IDENTIFIER" "[{type: UNKN" "[{type: UNKN" ...
##  $ Encryption.Status                 : chr  " UNENCRYPTED" " UNENCRYPTED" " UNKN" " UNKN" ...
##  $ Information.Types.Description     : chr  " The specific types of information affected were not disclosed in the provided breach notification letter, only"| __truncated__ " Names and contact information were involved in the breach.}" " No specific information about the types of information exposed in the breach is provided.}]" " The data breach notification provides no details regarding the types of information that were impacted in the breach.}]" ...
##  $ breach_location_street            : chr  "7901 Woodway Drive" "7950 Jones Branch Dr." "UNKN" "UNKN" ...
##  $ breach_location_city              : chr  "Waco" "McLean" "UNKN" "UNKN" ...
##  $ breach_location_state             : chr  "Texas" "Virginia" "UNKN" "New Jersey" ...
##  $ breach_location_ZIP               : chr  "76712" "22107" "UNKN" "7068" ...
##  $ breach_location_country           : chr  "United States" "United States" "United States" "United States" ...
##  $ breach_location_explanation       : chr  "The Data Breach Notification Letter provided a full address '7901 Woodway Drive, Waco, TX 76712' which is associated with STRAT "The location information is provided at the end of the data breach notification letter where Pat McClanahan, Vice President of  "The breach notification does not include specific location details such as street, city, state, or zip code. However, given the "The breach notification does not provide specific location details for Bed Bath & Beyond Inc. However, as of the knowledge cuto ...
##  $ Records.Impacted.From.Source      : chr  "UNKN" "UNKN" "14599" "UNKN" ...
##  $ State.Records.Impacted.from.Source: chr  "UNKN" "UNKN" "7" "1" ...
##  $ Notification.URL                  : chr  "https://oag.ca.gov/system/files/Frontier%20Bancshares%20-%20Consumer%20Notification%20Letter%20%28California%29_1.pdf" "https://www.iowaattorneygeneral.gov/media/cms/Gannett_Company_Inc_9F9AE233E1850.pdf" "" "" ...
##  $ PDF                               : chr  "https://databreacharchives.com/api/v1/pdf/CA\\strata_trust_company_0004903038160326516112cf45332a8c.pdf" "https://databreacharchives.com/api/v1/pdf/IA\\gannett_company_inc.pdf" "" "" ...
##  $ DBA.UUID                          : chr  "0004903038160326516112cf45332a8c" "0007779852bc8744eccf01b3c3d6d67a" "0007f1d701325fc8f0f07ce1d5286755" "0008303654e1c86c0a084229875c4953" ...
##  $ Date.of.Breach.End.Month          : chr  "2" "UNKN" "UNKN" "12" ...
##  $ Date.of.Breach.End.Day            : int  11 NA NA 8 NA 25 NA NA 5 18 ...
##  $ Date.of.Breach.End.Year           : int  2020 NA NA 2017 NA 2017 NA NA 2018 2021 ...
##  $ Total.Records.Impacted.From.Letter: chr  "UNKN" "18100" "UNKN" "UNKN" ...
##  $ State.Records.Impacted.from.Letter: chr  "UNKN" "634" "UNKN" "UNKN" ...
##  $ Description.of.Breach             : chr  "STRATA Trust Company experienced unusual activity involving an employee email account indicating unauthorized a"| __truncated__ "Gannett Company, Inc. experienced a phishing attack compromising the Office 365 and email account credentials o"| __truncated__ "The breach at Somerset County occurred on May 19, 2022, and was reported on September 28, 2022. There is no spe"| __truncated__ "Bed Bath & Beyond Inc. experienced a data breach between November 21, 2017, and December 8, 2017, which was dis"| __truncated__ ...
head(PRC2024)
##   Index            Name.of.Entity
## 1     1      STRATA Trust Company
## 2     2      Gannett Company Inc.
## 3     3           Somerset County
## 4     4    Bed Bath & Beyond Inc.
## 5     5 Angola Wire Products Inc.
## 6     6 State Industrial Products
##                                          Likely.Duplicate.Breach Source
## 1                                                                    CA
## 2      Gannett_Company_Inc.-6fc8ae0a-89e2-4dbd-8f05-cce0458c40f2     IA
## 3                                                                    IN
## 4    Bed_Bath_&_Beyond_Inc.-0bf67bf4-6042-4d0c-ae4d-52a8010cc0b6     ME
## 5 Angola_Wire_Products_Inc.-99932b50-fb4a-4a1b-96db-e65dafa46154     IN
## 6                                                                    ME
##   Reported.Date.Month Reported.Date.Day Reported.Date.Year Date.of.Breach.Month
## 1                   7                17               2020                    2
## 2                   4                28               2017                    3
## 3                   9                28               2022                    5
## 4                   2                13               2018                   11
## 5                   6                 2               2023                    1
## 6                   8                 4               2017                    2
##   Date.of.Breach.Day Date.of.Breach.Year Organization.Type
## 1                 10                2020               BSF
## 2                 30                2017               BSO
## 3                 19                2022               GOV
## 4                 21                2017               BSR
## 5                  5                2023               BSO
## 6                 16                2017               BSO
##                                                                                                                                                                                                                                               Organization.Type.Explanation
## 1                                                                                              STRATA Trust Company is a trust company, which suggests it manages investments, financial assets, and other services typically aligned with the financial services industry.
## 2                                                                                                                                               Gannett Company, Inc. is primarily a media company involved in publishing industries, making it a BSO type of organization.
## 3                                                                                                                                  The name 'Somerset County' indicates it is a local government entity, as counties are typically administrative divisions within a state.
## 4                                                                                                                                                                    Bed Bath & Beyond Inc. is a retail business, which falls into the category of Retail Businesses (BSR).
## 5                                                                               Angola Wire Products Inc. appears to be a manufacturing company, which classifies it as a 'BSO' since it falls under the Other Businesses category which includes manufacturing industries.
## 6 State Industrial Products does not clearly fit into the categories of finance, retail, education, government, or healthcare. Given the name, it likely falls under 'Other Businesses' which could be related to manufacturing or providing industrial goods and services.
##   Breach.Type Breach.Impute
## 1        HACK          HACK
## 2        HACK          HACK
## 3        UNKN          INSD
## 4        UNKN          CARD
## 5        UNKN          INSD
## 6        UNKN          INSD
##                                                                                                                                                                                                                                                                                                                                                                                                                                        Breach.Type.Explanation
## 1                                                                                                                                                                                 The description of the breach indicates that there was 'unusual activity involving an employee email account indicating unauthorized access', which suggests that the email account was compromised likely through a cyberattack or unauthorized access by an outside party.
## 2                                                                                                                                                                                                                The breach was a result of a phishing attack targeting HR department members which led to the compromise of Office 365 and email account credentials. This falls under the definition of a cyberattack involving unauthorized network access.
## 3                                                                                                                                                                                                                    The breach type is classified as 'UNKN' due to the lack of specific information given about the nature of the breach. Since no details are provided about how the data was compromised, it is not possible to assign another breach type.
## 4                                                                                                                                                                                                                                                                                                 Insufficient details about the breach are provided. It is unclear whether it was a hacking incident, an internal mistake, physical theft, or something else.
## 5 The data breach report does not provide specific information about how the breach occurred. The method and nature of the data compromise are not mentioned, and there is no data breach notification letter available to elaborate on the incident's details. Therefore, without additional information indicating whether it was an insider action, hacking incident, physical document loss, or any other breach type, it has to be classified as unknown.
## 6                                                                                                                                                                                                                                                                      The nature of the breach, including the methods of data exposure and the specific details of how the breach occurred, were not provided, thus the breach type is classified as Unknown.
##   Max.Records.Impacted
## 1                 UNKN
## 2                18100
## 3                14599
## 4                    1
## 5                 1085
## 6                    1
##                                                                                                                                                                                                                                                                                                                    Max.Records.Impacted.Explanation
## 1                                                                                                                          There is no numerical information provided in either the source or the letter regarding the total or state records impacted by the breach, so it is not possible to determine a specific number of max records impacted.
## 2                                                                                                                                               The data breach notification letter states that approximately 18,100 individuals were affected by this incident, which is the specific and highest figure given for the number of records impacted.
## 3                                                                                                              The maximum number of records impacted is determined to be 14,599, which is the total provided from the source. No additional information has been provided from the data breach notification letter to indicate a different number.
## 4                                                                                                                                             Although the total records impacted are not provided, the source states that there was 1 state record impacted. This is the only specific figure provided, so it is used as the max records impacted.
## 5 The source provided a total of 1,085 records impacted, and no specific count was provided in the data breach notification letter. The information regarding state records stands at 647, which is the lower figure. Therefore, the highest number available and confirmed is the total records impacted provided from the source, which is 1,085.
## 6                                                                                                                                              The source specified that 1 state resident was impacted, which is the only numeric data provided. No total records impacted was specified, therefore the max records impacted is determined to be 1.
##   Description  Information.Types Encryption.Status
## 1                   [{type: UNKN       UNENCRYPTED
## 2             [{type: IDENTIFIER       UNENCRYPTED
## 3                   [{type: UNKN              UNKN
## 4                   [{type: UNKN              UNKN
## 5                   [{type: UNKN              UNKN
## 6                   [{type: UNKN       UNENCRYPTED
##                                                                                                                                                    Information.Types.Description
## 1  The specific types of information affected were not disclosed in the provided breach notification letter, only a placeholder [Impacted Data] was mentioned without details.}]
## 2                                                                                                                    Names and contact information were involved in the breach.}
## 3                                                                                    No specific information about the types of information exposed in the breach is provided.}]
## 4                                                        The data breach notification provides no details regarding the types of information that were impacted in the breach.}]
## 5                                                                   The description does not provide any information regarding the types of information exposed in the breach.}]
## 6                                                                                      There is no information available about the types of information exposed in the breach.}]
##   breach_location_street breach_location_city breach_location_state
## 1     7901 Woodway Drive                 Waco                 Texas
## 2  7950 Jones Branch Dr.               McLean              Virginia
## 3                   UNKN                 UNKN                  UNKN
## 4                   UNKN                 UNKN            New Jersey
## 5                   UNKN                 UNKN               Indiana
## 6                   UNKN                 UNKN                  UNKN
##   breach_location_ZIP breach_location_country
## 1               76712           United States
## 2               22107           United States
## 3                UNKN           United States
## 4                7068           United States
## 5                UNKN           United States
## 6                UNKN                    UNKN
##                                                                                                                                                                                                                                                                                       breach_location_explanation
## 1                                                                               The Data Breach Notification Letter provided a full address '7901 Woodway Drive, Waco, TX 76712' which is associated with STRATA Trust Company as the sender of the notification, indicating the location of the breached entity.
## 2                                                                                                                    The location information is provided at the end of the data breach notification letter where Pat McClanahan, Vice President of People Operations at GANNETT, lists an address in McLean, VA.
## 3 The breach notification does not include specific location details such as street, city, state, or zip code. However, given the name 'Somerset County,' it can be inferred that the entity belongs to the United States, but without more information, we cannot specify the exact location within the country.
## 4                                                               The breach notification does not provide specific location details for Bed Bath & Beyond Inc. However, as of the knowledge cutoff in 2023, the company is known to have its corporate headquarters in Union, New Jersey, with the zip code 07068.
## 5                            The breach notification source indicates that the entity is located in Indiana, but no specific city, street address, or zip code is provided. Angola Wire Products Inc. does not have widely known location details as of 2023, which would allow us to specify the city or street.
## 6                                                                                                                                                 The breach notification does not include any specific location details such as street address, city, state, zip code, or country for State Industrial Products.
##   Records.Impacted.From.Source State.Records.Impacted.from.Source
## 1                         UNKN                               UNKN
## 2                         UNKN                               UNKN
## 3                        14599                                  7
## 4                         UNKN                                  1
## 5                         1085                                647
## 6                         UNKN                                  1
##                                                                                                        Notification.URL
## 1 https://oag.ca.gov/system/files/Frontier%20Bancshares%20-%20Consumer%20Notification%20Letter%20%28California%29_1.pdf
## 2                                   https://www.iowaattorneygeneral.gov/media/cms/Gannett_Company_Inc_9F9AE233E1850.pdf
## 3                                                                                                                      
## 4                                                                                                                      
## 5                                                                                                                      
## 6                                                                                                                      
##                                                                                                       PDF
## 1 https://databreacharchives.com/api/v1/pdf/CA\\strata_trust_company_0004903038160326516112cf45332a8c.pdf
## 2                                   https://databreacharchives.com/api/v1/pdf/IA\\gannett_company_inc.pdf
## 3                                                                                                        
## 4                                                                                                        
## 5                                                                                                        
## 6                                                                                                        
##                           DBA.UUID Date.of.Breach.End.Month
## 1 0004903038160326516112cf45332a8c                        2
## 2 0007779852bc8744eccf01b3c3d6d67a                     UNKN
## 3 0007f1d701325fc8f0f07ce1d5286755                     UNKN
## 4 0008303654e1c86c0a084229875c4953                       12
## 5 0008f6856a8bf95420aa1b595be86e15                     UNKN
## 6 000b4f2a02d93e613730ec1deaeca4c8                        6
##   Date.of.Breach.End.Day Date.of.Breach.End.Year
## 1                     11                    2020
## 2                     NA                      NA
## 3                     NA                      NA
## 4                      8                    2017
## 5                     NA                      NA
## 6                     25                    2017
##   Total.Records.Impacted.From.Letter State.Records.Impacted.from.Letter
## 1                               UNKN                               UNKN
## 2                              18100                                634
## 3                               UNKN                               UNKN
## 4                               UNKN                               UNKN
## 5                               UNKN                               UNKN
## 6                               UNKN                               UNKN
##                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                             Description.of.Breach
## 1                                                                                                                                                                                                                      STRATA Trust Company experienced unusual activity involving an employee email account indicating unauthorized access. The incident could have occurred in early February 2020, potentially impacting personal information. Complimentary identity monitoring services are being offered to those affected.
## 2 Gannett Company, Inc. experienced a phishing attack compromising the Office 365 and email account credentials of several HR department members on March 30, 2017. There was an attempt at a fraudulent wire transfer using these credentials. Personal data of employees and beneficiaries were potentially accessed, including contact information, SSNs, dates of birth, bank details, and employment-related information. Gannett alerted employees, locked down affected accounts, and provided credit monitoring services.
## 3                                                                                                                                                                                                                            The breach at Somerset County occurred on May 19, 2022, and was reported on September 28, 2022. There is no specific information given about the nature of the breach or the types of information compromised. The total records impacted as per the source is 14,599 and impacts 7 state residents.
## 4                                                                                                                                                                         Bed Bath & Beyond Inc. experienced a data breach between November 21, 2017, and December 8, 2017, which was discovered on February 6, 2018. The breach was reported on February 13, 2018. As there is no detailed information available in the notification, it is unclear what happened, who was impacted, and what specific information was affected.
## 5                                                                                                                                                                                                                                                         The breach occurred on January 5, 2023, and was reported on June 2, 2023. Specific details of the breach, including how it happened and the information impacted, are not provided. It has been publicly reported through a data breach notification source in Indiana.
## 6                                                                                                                                                                                                                                        The breach occurred at State Industrial Products between February 16, 2017, and June 25, 2017. It was reported on August 4, 2017. There is currently no information available regarding the nature of the breach, the number of records impacted or the type of information compromised.

Rename variables

# Assuming 'my_data' is your data frame
# And you want to rename 'old_name1' to 'new_name1' and 'old_name2' to 'new_name2'



# Rename columns
colnames(PRC2024) <- c("Index", "Name.of.Entity", "Likely.Duplicate.Breach", "Source", "Reported.Date.Month","Reported.Date.Day","Reported.Date.Year","Date.of.Breach.Month", "Date.of.Breach.Day", "Date.of.Breach.Year", "Organization.Type", "Organization.Type.Explanation", "Breach.Type","Breach.Impute" ,"Breach.Type.Explanation", "Max.Records.Impacted", "Max.Records.Impacted.Explanation", "Description", "Information.Types","Encryption.Status","Information.Types.Description", "breach.location.street", "breach.location.city", "State", "breach.location.ZIP","breach.location.country","breach.location.explanation", "Records.Impacted.From.Source", "State.Records.Impacted.from.Source", "Notification.URL", "PDF", "DBAUUID", "Date.of.Breach.End.Month","Date.of.Breach.End.Day","Date.of.Breach.End.Year","Total.Records.Impacted.From.Letter", "State.Records.Impacted.from.Letter", "Description.of.Breach" )

# View the data frame with the new column names

Code to transform the variables in the PRC2024 dataset from factors to their corresponding variable types.

PRC2024$Index <- as.numeric(PRC2024$Index)
PRC2024$Name.of.Entity <- as.character(PRC2024$Name.of.Entity)
PRC2024$Likely.Duplicate.Breach <- as.character(PRC2024$Likely.Duplicate.Breach)
PRC2024$Source <- as.character(PRC2024$Source)
PRC2024$Reported.Date.Month <- as.numeric(PRC2024$Reported.Date.Month)
## Warning: NAs introduced by coercion
PRC2024$Reported.Date.Day <- as.numeric(PRC2024$Reported.Date.Day)
PRC2024$Reported.Date.Year <- as.numeric(PRC2024$Reported.Date.Year)
PRC2024$Date.of.Breach.Month <- as.numeric(PRC2024$Date.of.Breach.End.Month)
## Warning: NAs introduced by coercion
PRC2024$Date.of.Breach.Day <- as.numeric(PRC2024$Date.of.Breach.End.Day)
PRC2024$Date.of.Breach.Year <- as.numeric(PRC2024$Date.of.Breach.End.Year)
PRC2024$Organization.Type <- as.character(PRC2024$Organization.Type)
PRC2024$Organization.Type.Explanation <- as.character(PRC2024$Organization.Type.Explanation)
PRC2024$Breach.Type <- as.character(PRC2024$Breach.Type)
PRC2024$Breach.Impute <- as.character(PRC2024$Breach.Impute)
PRC2024$Breach.Type.Explanation <- as.character(PRC2024$Breach.Type.Explanation)
PRC2024$Max.Records.Impacted <- as.numeric(PRC2024$Max.Records.Impacted) 
## Warning: NAs introduced by coercion
PRC2024$Max.Records.Impacted.Explanation <- as.character(PRC2024$Max.Records.Impacted.Explanation)
PRC2024$Information.Types <- as.character(PRC2024$Information.Types)
PRC2024$Encryption.Status <- as.character(PRC2024$Encryption.Status)
PRC2024$Information.Types.Description <- as.character(PRC2024$Information.Types.Description)
PRC2024$Description <- as.character(PRC2024$Description)
PRC2024$breach.location.street <- as.character(PRC2024$breach.location.street)
PRC2024$breach.location.city <- as.character(PRC2024$breach.location.city)
PRC2024$State <- as.character(PRC2024$State)
PRC2024$breach.location.ZIP <- as.numeric(PRC2024$breach.location.ZIP)
## Warning: NAs introduced by coercion
PRC2024$breach.location.country <- as.character(PRC2024$breach.location.country)
PRC2024$breach.location.explanation <- as.character(PRC2024$breach.location.explanation)
PRC2024$Records.Impacted.From.Source <- as.character(PRC2024$Records.Impacted.From.Source)
PRC2024$State.Records.Impacted.from.Source <- as.numeric(PRC2024$State.Records.Impacted.from.Source)
## Warning: NAs introduced by coercion
PRC2024$Notification.URL <- as.numeric(PRC2024$Notification.URL)
## Warning: NAs introduced by coercion
PRC2024$PDF <- as.character(PRC2024$PDF)
PRC2024$DBAUUID <- as.character(PRC2024$DBAUUID)
PRC2024$Date.of.Breach.End.Month <- as.numeric(PRC2024$Date.of.Breach.End.Month)
## Warning: NAs introduced by coercion
PRC2024$Date.of.Breach.End.Day <- as.numeric(PRC2024$Date.of.Breach.End.Day)
PRC2024$Date.of.Breach.End.Year <- as.numeric(PRC2024$Date.of.Breach.End.Year)
PRC2024$Total.Records.Impacted.From.Letter<- as.numeric(PRC2024$Total.Records.Impacted.From.Letter)
## Warning: NAs introduced by coercion
PRC2024$State.Records.Impacted.from.Letter<- as.numeric(PRC2024$State.Records.Impacted.from.Letter)
## Warning: NAs introduced by coercion
PRC2024$Description.of.Breach <- as.character(PRC2024$Description.of.Breach)

Merge Reported dates into one column and format correctly

# Combine the month, day, and year columns into a single date string
PRC2024$Reported.Date.String <- with(PRC2024, paste(Reported.Date.Month, Reported.Date.Day, Reported.Date.Year, sep = "/"))

# Convert the date string into a Date object
PRC2024$Reported.Date.New <- as.Date(PRC2024$Reported.Date.String, format = "%m/%d/%Y")

# The 'date' column will now have the combined dates in yyyy/mm/dd format

Merge Date of Breach into one column and format correctly

# Combine the month, day, and year columns into a single date string
PRC2024$Date.of.Breach.String <- with(PRC2024, paste(Date.of.Breach.Month, Date.of.Breach.Day, Date.of.Breach.Year, sep = "/"))

# Convert the date string into a Date object
PRC2024$Date.of.Breach.New <- as.Date(PRC2024$Date.of.Breach.String, format = "%m/%d/%Y")

# The 'date' column will now have the combined dates in yyyy/mm/dd format

Merge Date of Breach End into one column and format correctly

# Combine the month, day, and year columns into a single date string
PRC2024$Date.of.Breach.End.String <- with(PRC2024, paste(Date.of.Breach.End.Month, Date.of.Breach.End.Day, Date.of.Breach.End.Year, sep = "/"))

# Convert the date string into a Date object
PRC2024$Date.of.Breach.End.New <- as.Date(PRC2024$Date.of.Breach.End.String, format = "%m/%d/%Y")

# The 'date' column will now have the combined dates in yyyy/mm/dd format

Checking for missing values count

# Check for NAs in the entire dataset
nas_in_dataset <- colSums(is.na(PRC2024))

# Print the result
print(nas_in_dataset)
##                              Index                     Name.of.Entity 
##                                  0                                  0 
##            Likely.Duplicate.Breach                             Source 
##                                  0                                  0 
##                Reported.Date.Month                  Reported.Date.Day 
##                                558                                621 
##                 Reported.Date.Year               Date.of.Breach.Month 
##                               2488                              23915 
##                 Date.of.Breach.Day                Date.of.Breach.Year 
##                              23937                              24588 
##                  Organization.Type      Organization.Type.Explanation 
##                                  0                                  0 
##                        Breach.Type                      Breach.Impute 
##                                  0                                  0 
##            Breach.Type.Explanation               Max.Records.Impacted 
##                                  0                               8123 
##   Max.Records.Impacted.Explanation                        Description 
##                                  0                                  0 
##                  Information.Types                  Encryption.Status 
##                                  0                                  0 
##      Information.Types.Description             breach.location.street 
##                                  0                                  0 
##               breach.location.city                              State 
##                                  0                                  0 
##                breach.location.ZIP            breach.location.country 
##                              27208                                  0 
##        breach.location.explanation       Records.Impacted.From.Source 
##                                  0                                  0 
## State.Records.Impacted.from.Source                   Notification.URL 
##                              17254                              35167 
##                                PDF                            DBAUUID 
##                                  0                                  0 
##           Date.of.Breach.End.Month             Date.of.Breach.End.Day 
##                              23915                              23937 
##            Date.of.Breach.End.Year Total.Records.Impacted.From.Letter 
##                              24588                              32355 
## State.Records.Impacted.from.Letter              Description.of.Breach 
##                              29124                                  0 
##               Reported.Date.String                  Reported.Date.New 
##                                  0                               2489 
##              Date.of.Breach.String                 Date.of.Breach.New 
##                                  0                              24589 
##          Date.of.Breach.End.String             Date.of.Breach.End.New 
##                                  0                              24589

Remove not needed variables:

PRC2024$Likely.Duplicate.Breach <- NULL #Will remove Likely duplicate column
PRC2024$Reported.Date.Month <- NULL #
PRC2024$Reported.Date.Day <- NULL
PRC2024$Reported.Date.Year <- NULL
PRC2024$Date.of.Breach.Month <- NULL
PRC2024$Date.of.Breach.Day <- NULL
PRC2024$Date.of.Breach.Year <- NULL
PRC2024$Description <- NULL
PRC2024$breach.location.street <- NULL
PRC2024$breach.location.city <- NULL
PRC2024$breach.location.ZIP <- NULL
PRC2024$breach.location.country <- NULL
PRC2024$Records.Impacted.From.Source <- NULL
PRC2024$State.Records.Impacted.from.Source <- NULL
PRC2024$PDF <- NULL
PRC2024$Notification.URL <- NULL
PRC2024$DBAUUID <- NULL
PRC2024$Date.of.Breach.End.Day <- NULL
PRC2024$Date.of.Breach.End.Month <- NULL
PRC2024$Date.of.Breach.End.Year <- NULL
PRC2024$Total.Records.Impacted.From.Letter <- NULL
PRC2024$State.Records.Impacted.from.Source <- NULL
PRC2024$State.Records.Impacted.from.Letter <- NULL

Replaces UNKN and various words on the State column with equivalent in source column

PRC2024$State <- ifelse(PRC2024$State == "UNKN", PRC2024$Source, PRC2024$State)
PRC2024$State <- ifelse(PRC2024$State == "Unknown", PRC2024$Source, PRC2024$State)
PRC2024$State <- ifelse(PRC2024$State == "Newfoundland and Labrador", PRC2024$Source, PRC2024$State)
PRC2024$State <- ifelse(PRC2024$State == "BC", PRC2024$Source, PRC2024$State)
PRC2024$State <- ifelse(PRC2024$State == "Berlin", PRC2024$Source, PRC2024$State)
PRC2024$State <- ifelse(PRC2024$State == "British Columbia", PRC2024$Source, PRC2024$State)
PRC2024$State <- ifelse(PRC2024$State == "Guangdong", PRC2024$Source, PRC2024$State)
PRC2024$State <- ifelse(PRC2024$State == "New Providence", PRC2024$Source, PRC2024$State)
PRC2024$State <- ifelse(PRC2024$State == "NS", PRC2024$Source, PRC2024$State)
PRC2024$State <- ifelse(PRC2024$State == "Oneonta", PRC2024$Source, PRC2024$State)
PRC2024$State <- ifelse(PRC2024$State == "Ontario", PRC2024$Source, PRC2024$State)
PRC2024$State <- ifelse(PRC2024$State == "QC", PRC2024$Source, PRC2024$State)
PRC2024$State <- ifelse(PRC2024$State == "Quebec", PRC2024$Source, PRC2024$State)
PRC2024$State <- ifelse(PRC2024$State == "San Diego", PRC2024$Source, PRC2024$State)
PRC2024$State <- ifelse(PRC2024$State == "State", PRC2024$Source, PRC2024$State)
PRC2024$State <- ifelse(PRC2024$State == "Zurich", PRC2024$Source, PRC2024$State)

Substitute two letter states for full State name

PRC2024$State <- sub('HHS', 'Unkn', PRC2024$State)
PRC2024$State <- sub('CA', 'California', PRC2024$State)
PRC2024$State <- sub('DC', 'District of Columbia', PRC2024$State)
PRC2024$State <- sub('DE', 'Delaware', PRC2024$State)
PRC2024$State <- sub('NH', 'New Hampshire', PRC2024$State)
PRC2024$State <- sub('IA', 'Iowa', PRC2024$State)
PRC2024$State <- sub('IN', 'Indiana', PRC2024$State)
PRC2024$State <- sub('MT', 'Montana', PRC2024$State)
PRC2024$State <- sub('MA', 'Massachusetts', PRC2024$State)
PRC2024$State <- sub('MD', 'Maryland', PRC2024$State)
PRC2024$State <- sub('ME', 'Maine', PRC2024$State)
PRC2024$State <- sub('VT', 'Vermont', PRC2024$State)
PRC2024$State <- sub('WA', 'Washington', PRC2024$State)
PRC2024$State <- sub('WI', 'California', PRC2024$State)
PRC2024$State <- sub('TX', 'Texas', PRC2024$State)
PRC2024$State <- sub('NJ', 'New Jersey', PRC2024$State)
PRC2024$State <- sub('OR', 'Oregon', PRC2024$State)

Split rows in the State column

library(tidyr)

# Use separate_rows to split the 'state' column, considering both comma and semicolon as delimiters
PRC2024 <- separate_rows(PRC2024, State, sep = "[,;]|\\band\\b")

# Trim whitespace that might occur after splitting
PRC2024$State <- trimws(PRC2024$State)

This code removes [{“type”: from the beginning of the colum information type:

# Remove '[{"type":' from the beginning of each string in the specified column
PRC2024$Information.Types <- sub("^\\[\\{type:", "", PRC2024$Information.Types)
PRC2024$Information.Types <- gsub("\\[UNKN\\]", "UNKN", PRC2024$Information.Types)

Removes “Encryption status from beggining of encrytion status colums

# Remove 'encryption_status' from the beginning of each entry in 'column_name'
PRC2024$Encryption.Status <- sub("^encryption_status:", "", PRC2024$Encryption.Status)

Trims extra spaces in character columns

# Loop over all columns in the dataframe
PRC2024 <- lapply(PRC2024, function(x) {
  # Check if the column is of character type
  if (is.character(x)) {
    # Attempt to convert encoding to UTF-8
    x <- iconv(x, from = "latin1", to = "UTF-8", sub = "byte")
    # Trim leading and trailing spaces from the character column
    x <- trimws(x)
  }
  return(x)
})

# Convert the list back to a dataframe
PRC2024 <- as.data.frame(PRC2024)

Removes [] from information types column

PRC2024$Information.Types <- gsub("\\[]", "", PRC2024$Information.Types)

Replacing empty strings with NA’s

PRC2024[PRC2024 == ''] <- NA 

Replace NA values in columns with ‘unkn’

# Replace NA values in column 'a' with 'unkn'
PRC2024$Information.Types[is.na(PRC2024$Information.Types)] <- "UNKN"
PRC2024$Encryption.Status[is.na(PRC2024$Encryption.Status)] <- "UNKN"
PRC2024$Information.Types.Description[is.na(PRC2024$Information.Types.Description)] <- "UNKN"
PRC2024$Description.of.Breach[is.na(PRC2024$Description.of.Breach)] <- "UNKN"

Count how many NAs in dataset

# Check for NAs in the entire dataset
nas_in_dataset <- colSums(is.na(PRC2024))

# Print the result
print(nas_in_dataset)
##                            Index                   Name.of.Entity 
##                                0                                0 
##                           Source                Organization.Type 
##                                0                                0 
##    Organization.Type.Explanation                      Breach.Type 
##                                0                                0 
##                    Breach.Impute          Breach.Type.Explanation 
##                                0                                0 
##             Max.Records.Impacted Max.Records.Impacted.Explanation 
##                             8124                                0 
##                Information.Types                Encryption.Status 
##                                0                                0 
##    Information.Types.Description                            State 
##                                0                                0 
##      breach.location.explanation            Description.of.Breach 
##                                0                                0 
##             Reported.Date.String                Reported.Date.New 
##                                0                             2489 
##            Date.of.Breach.String               Date.of.Breach.New 
##                                0                            24592 
##        Date.of.Breach.End.String           Date.of.Breach.End.New 
##                                0                            24592
head(PRC2024)
##   Index            Name.of.Entity Source Organization.Type
## 1     1      STRATA Trust Company     CA               BSF
## 2     2      Gannett Company Inc.     IA               BSO
## 3     3           Somerset County     IN               GOV
## 4     4    Bed Bath & Beyond Inc.     ME               BSR
## 5     5 Angola Wire Products Inc.     IN               BSO
## 6     6 State Industrial Products     ME               BSO
##                                                                                                                                                                                                                                               Organization.Type.Explanation
## 1                                                                                              STRATA Trust Company is a trust company, which suggests it manages investments, financial assets, and other services typically aligned with the financial services industry.
## 2                                                                                                                                               Gannett Company, Inc. is primarily a media company involved in publishing industries, making it a BSO type of organization.
## 3                                                                                                                                  The name 'Somerset County' indicates it is a local government entity, as counties are typically administrative divisions within a state.
## 4                                                                                                                                                                    Bed Bath & Beyond Inc. is a retail business, which falls into the category of Retail Businesses (BSR).
## 5                                                                               Angola Wire Products Inc. appears to be a manufacturing company, which classifies it as a 'BSO' since it falls under the Other Businesses category which includes manufacturing industries.
## 6 State Industrial Products does not clearly fit into the categories of finance, retail, education, government, or healthcare. Given the name, it likely falls under 'Other Businesses' which could be related to manufacturing or providing industrial goods and services.
##   Breach.Type Breach.Impute
## 1        HACK          HACK
## 2        HACK          HACK
## 3        UNKN          INSD
## 4        UNKN          CARD
## 5        UNKN          INSD
## 6        UNKN          INSD
##                                                                                                                                                                                                                                                                                                                                                                                                                                        Breach.Type.Explanation
## 1                                                                                                                                                                                 The description of the breach indicates that there was 'unusual activity involving an employee email account indicating unauthorized access', which suggests that the email account was compromised likely through a cyberattack or unauthorized access by an outside party.
## 2                                                                                                                                                                                                                The breach was a result of a phishing attack targeting HR department members which led to the compromise of Office 365 and email account credentials. This falls under the definition of a cyberattack involving unauthorized network access.
## 3                                                                                                                                                                                                                    The breach type is classified as 'UNKN' due to the lack of specific information given about the nature of the breach. Since no details are provided about how the data was compromised, it is not possible to assign another breach type.
## 4                                                                                                                                                                                                                                                                                                 Insufficient details about the breach are provided. It is unclear whether it was a hacking incident, an internal mistake, physical theft, or something else.
## 5 The data breach report does not provide specific information about how the breach occurred. The method and nature of the data compromise are not mentioned, and there is no data breach notification letter available to elaborate on the incident's details. Therefore, without additional information indicating whether it was an insider action, hacking incident, physical document loss, or any other breach type, it has to be classified as unknown.
## 6                                                                                                                                                                                                                                                                      The nature of the breach, including the methods of data exposure and the specific details of how the breach occurred, were not provided, thus the breach type is classified as Unknown.
##   Max.Records.Impacted
## 1                   NA
## 2                18100
## 3                14599
## 4                    1
## 5                 1085
## 6                    1
##                                                                                                                                                                                                                                                                                                                    Max.Records.Impacted.Explanation
## 1                                                                                                                          There is no numerical information provided in either the source or the letter regarding the total or state records impacted by the breach, so it is not possible to determine a specific number of max records impacted.
## 2                                                                                                                                               The data breach notification letter states that approximately 18,100 individuals were affected by this incident, which is the specific and highest figure given for the number of records impacted.
## 3                                                                                                              The maximum number of records impacted is determined to be 14,599, which is the total provided from the source. No additional information has been provided from the data breach notification letter to indicate a different number.
## 4                                                                                                                                             Although the total records impacted are not provided, the source states that there was 1 state record impacted. This is the only specific figure provided, so it is used as the max records impacted.
## 5 The source provided a total of 1,085 records impacted, and no specific count was provided in the data breach notification letter. The information regarding state records stands at 647, which is the lower figure. Therefore, the highest number available and confirmed is the total records impacted provided from the source, which is 1,085.
## 6                                                                                                                                              The source specified that 1 state resident was impacted, which is the only numeric data provided. No total records impacted was specified, therefore the max records impacted is determined to be 1.
##   Information.Types Encryption.Status
## 1              UNKN       UNENCRYPTED
## 2        IDENTIFIER       UNENCRYPTED
## 3              UNKN              UNKN
## 4              UNKN              UNKN
## 5              UNKN              UNKN
## 6              UNKN       UNENCRYPTED
##                                                                                                                                                   Information.Types.Description
## 1 The specific types of information affected were not disclosed in the provided breach notification letter, only a placeholder [Impacted Data] was mentioned without details.}]
## 2                                                                                                                   Names and contact information were involved in the breach.}
## 3                                                                                   No specific information about the types of information exposed in the breach is provided.}]
## 4                                                       The data breach notification provides no details regarding the types of information that were impacted in the breach.}]
## 5                                                                  The description does not provide any information regarding the types of information exposed in the breach.}]
## 6                                                                                     There is no information available about the types of information exposed in the breach.}]
##        State
## 1      Texas
## 2   Virginia
## 3    Indiana
## 4 New Jersey
## 5    Indiana
## 6      Maine
##                                                                                                                                                                                                                                                                                       breach.location.explanation
## 1                                                                               The Data Breach Notification Letter provided a full address '7901 Woodway Drive, Waco, TX 76712' which is associated with STRATA Trust Company as the sender of the notification, indicating the location of the breached entity.
## 2                                                                                                                    The location information is provided at the end of the data breach notification letter where Pat McClanahan, Vice President of People Operations at GANNETT, lists an address in McLean, VA.
## 3 The breach notification does not include specific location details such as street, city, state, or zip code. However, given the name 'Somerset County,' it can be inferred that the entity belongs to the United States, but without more information, we cannot specify the exact location within the country.
## 4                                                               The breach notification does not provide specific location details for Bed Bath & Beyond Inc. However, as of the knowledge cutoff in 2023, the company is known to have its corporate headquarters in Union, New Jersey, with the zip code 07068.
## 5                            The breach notification source indicates that the entity is located in Indiana, but no specific city, street address, or zip code is provided. Angola Wire Products Inc. does not have widely known location details as of 2023, which would allow us to specify the city or street.
## 6                                                                                                                                                 The breach notification does not include any specific location details such as street address, city, state, zip code, or country for State Industrial Products.
##                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                             Description.of.Breach
## 1                                                                                                                                                                                                                      STRATA Trust Company experienced unusual activity involving an employee email account indicating unauthorized access. The incident could have occurred in early February 2020, potentially impacting personal information. Complimentary identity monitoring services are being offered to those affected.
## 2 Gannett Company, Inc. experienced a phishing attack compromising the Office 365 and email account credentials of several HR department members on March 30, 2017. There was an attempt at a fraudulent wire transfer using these credentials. Personal data of employees and beneficiaries were potentially accessed, including contact information, SSNs, dates of birth, bank details, and employment-related information. Gannett alerted employees, locked down affected accounts, and provided credit monitoring services.
## 3                                                                                                                                                                                                                            The breach at Somerset County occurred on May 19, 2022, and was reported on September 28, 2022. There is no specific information given about the nature of the breach or the types of information compromised. The total records impacted as per the source is 14,599 and impacts 7 state residents.
## 4                                                                                                                                                                         Bed Bath & Beyond Inc. experienced a data breach between November 21, 2017, and December 8, 2017, which was discovered on February 6, 2018. The breach was reported on February 13, 2018. As there is no detailed information available in the notification, it is unclear what happened, who was impacted, and what specific information was affected.
## 5                                                                                                                                                                                                                                                         The breach occurred on January 5, 2023, and was reported on June 2, 2023. Specific details of the breach, including how it happened and the information impacted, are not provided. It has been publicly reported through a data breach notification source in Indiana.
## 6                                                                                                                                                                                                                                        The breach occurred at State Industrial Products between February 16, 2017, and June 25, 2017. It was reported on August 4, 2017. There is currently no information available regarding the nature of the breach, the number of records impacted or the type of information compromised.
##   Reported.Date.String Reported.Date.New Date.of.Breach.String
## 1            7/17/2020        2020-07-17             2/11/2020
## 2            4/28/2017        2017-04-28              NA/NA/NA
## 3            9/28/2022        2022-09-28              NA/NA/NA
## 4            2/13/2018        2018-02-13             12/8/2017
## 5             6/2/2023        2023-06-02              NA/NA/NA
## 6             8/4/2017        2017-08-04             6/25/2017
##   Date.of.Breach.New Date.of.Breach.End.String Date.of.Breach.End.New
## 1         2020-02-11                 2/11/2020             2020-02-11
## 2               <NA>                  NA/NA/NA                   <NA>
## 3               <NA>                  NA/NA/NA                   <NA>
## 4         2017-12-08                 12/8/2017             2017-12-08
## 5               <NA>                  NA/NA/NA                   <NA>
## 6         2017-06-25                 6/25/2017             2017-06-25

Removes the date of breach end of the dataset

PRC2024$Date.of.Breach.End.New <- NULL
PRC2024$Date.of.Breach.End.String <- NULL

**

Because there too many NAs in the date of breach (24,592) and reported date (2,489) then the following must be applied depending on if the organization is a MED organization or not:

Let’s break down the requirements:

If the Organization.Type = MED and If Max.Records.Impacted >= 500 and If Reported.Date.New is NA and Date.of.Breach.New is not NA then Reported.Date.New = Date.of.Breach.New + 60 days

If the Organization.Type = MED and If Max.Records.Impacted >= 500 and If Reported.Date.New is not NA and If Date.of.Breach.New is NA then Date.of.Breach.New = Reported.Date.New - 60 days

If the Organization.Type is not MED and If Reported.Date.New is NA and Date.of.Breach.New is not NA then Reported.Date.New = Date.of.Breach.New + 30 days

If the Organization.Type is not MED and If Reported.Date.New is not NA and If Date.of.Breach.New is NA then Date.of.Breach.New = Reported.Date.New - 30 days

for (i in 1:nrow(PRC2024)) {
  if (PRC2024$Organization.Type[i] == "MED" && !is.na(PRC2024$Max.Records.Impacted[i]) && PRC2024$Max.Records.Impacted[i] >= 500) {
    if (is.na(PRC2024$Reported.Date.New[i]) && !is.na(PRC2024$Date.of.Breach.New[i])) {
      PRC2024$Reported.Date.New[i] <- PRC2024$Date.of.Breach.New[i] + as.difftime(60, units = "days")
    } else if (!is.na(PRC2024$Reported.Date.New[i]) && is.na(PRC2024$Date.of.Breach.New[i])) {
      PRC2024$Date.of.Breach.New[i] <- PRC2024$Reported.Date.New[i] - as.difftime(60, units = "days")
    }
  } else if (PRC2024$Organization.Type[i] != "MED") {
    if (is.na(PRC2024$Reported.Date.New[i]) && !is.na(PRC2024$Date.of.Breach.New[i])) {
      PRC2024$Reported.Date.New[i] <- PRC2024$Date.of.Breach.New[i] + as.difftime(30, units = "days")
    } else if (!is.na(PRC2024$Reported.Date.New[i]) && is.na(PRC2024$Date.of.Breach.New[i])) {
      PRC2024$Date.of.Breach.New[i] <- PRC2024$Reported.Date.New[i] - as.difftime(30, units = "days")
    }
  }
}
na_countDateBreach3 <- sum(is.na(PRC2024$Date.of.Breach.New))
na_countReported3 <- sum(is.na(PRC2024$Reported.Date.New))
print(na_countDateBreach3)
## [1] 3736
print(na_countReported3)
## [1] 1891

[1] 3736 [1] 1891

After the above code the number of NA’s in the date of breach new and the reported date are 3,736 and 1,891 respectively.

The new NA’s for MED organizations still 1319.

Extract the year from the date of breach column string since this column has more complete years

library(stringr)
PRC2024$Year.of.Breach <- str_extract(PRC2024$Date.of.Breach.New, "\\d{4}")
PRC2024$Year.of.Breach <- as.numeric(PRC2024$Year.of.Breach)

Removes String columns

PRC2024$Date.of.Breach.String <- NULL
PRC2024$Reported.Date.String <- NULL

Clean up to the information types variable after EDA noted that the variables had empty strings that had to be corrected.

#PRC2024_clean$Information.Types[PRC2024_clean$Information.Types == ''] <- NA 

Removes NA’s from Dataset

PRC2024_cleanV2 <- na.omit(PRC2024)
head(PRC2024_cleanV2)
##   Index            Name.of.Entity Source Organization.Type
## 2     2      Gannett Company Inc.     IA               BSO
## 3     3           Somerset County     IN               GOV
## 4     4    Bed Bath & Beyond Inc.     ME               BSR
## 5     5 Angola Wire Products Inc.     IN               BSO
## 6     6 State Industrial Products     ME               BSO
## 7     7  CCS Technology Group LLC     IN               BSO
##                                                                                                                                                                                                                                               Organization.Type.Explanation
## 2                                                                                                                                               Gannett Company, Inc. is primarily a media company involved in publishing industries, making it a BSO type of organization.
## 3                                                                                                                                  The name 'Somerset County' indicates it is a local government entity, as counties are typically administrative divisions within a state.
## 4                                                                                                                                                                    Bed Bath & Beyond Inc. is a retail business, which falls into the category of Retail Businesses (BSR).
## 5                                                                               Angola Wire Products Inc. appears to be a manufacturing company, which classifies it as a 'BSO' since it falls under the Other Businesses category which includes manufacturing industries.
## 6 State Industrial Products does not clearly fit into the categories of finance, retail, education, government, or healthcare. Given the name, it likely falls under 'Other Businesses' which could be related to manufacturing or providing industrial goods and services.
## 7                                                                                                                                      CCS Technology Group LLC seems to be an IT or technology services firm, which typically falls under the 'Other Businesses' category.
##   Breach.Type Breach.Impute
## 2        HACK          HACK
## 3        UNKN          INSD
## 4        UNKN          CARD
## 5        UNKN          INSD
## 6        UNKN          INSD
## 7        UNKN          INSD
##                                                                                                                                                                                                                                                                                                                                                                                                                                        Breach.Type.Explanation
## 2                                                                                                                                                                                                                The breach was a result of a phishing attack targeting HR department members which led to the compromise of Office 365 and email account credentials. This falls under the definition of a cyberattack involving unauthorized network access.
## 3                                                                                                                                                                                                                    The breach type is classified as 'UNKN' due to the lack of specific information given about the nature of the breach. Since no details are provided about how the data was compromised, it is not possible to assign another breach type.
## 4                                                                                                                                                                                                                                                                                                 Insufficient details about the breach are provided. It is unclear whether it was a hacking incident, an internal mistake, physical theft, or something else.
## 5 The data breach report does not provide specific information about how the breach occurred. The method and nature of the data compromise are not mentioned, and there is no data breach notification letter available to elaborate on the incident's details. Therefore, without additional information indicating whether it was an insider action, hacking incident, physical document loss, or any other breach type, it has to be classified as unknown.
## 6                                                                                                                                                                                                                                                                      The nature of the breach, including the methods of data exposure and the specific details of how the breach occurred, were not provided, thus the breach type is classified as Unknown.
## 7                                                                                                                                                                                                                                                                                                          Details of the breach for CCS Technology Group LLC have not been provided, therefore it is impossible to appropriately classify the type of breach.
##   Max.Records.Impacted
## 2                18100
## 3                14599
## 4                    1
## 5                 1085
## 6                    1
## 7                    1
##                                                                                                                                                                                                                                                                                                                    Max.Records.Impacted.Explanation
## 2                                                                                                                                               The data breach notification letter states that approximately 18,100 individuals were affected by this incident, which is the specific and highest figure given for the number of records impacted.
## 3                                                                                                              The maximum number of records impacted is determined to be 14,599, which is the total provided from the source. No additional information has been provided from the data breach notification letter to indicate a different number.
## 4                                                                                                                                             Although the total records impacted are not provided, the source states that there was 1 state record impacted. This is the only specific figure provided, so it is used as the max records impacted.
## 5 The source provided a total of 1,085 records impacted, and no specific count was provided in the data breach notification letter. The information regarding state records stands at 647, which is the lower figure. Therefore, the highest number available and confirmed is the total records impacted provided from the source, which is 1,085.
## 6                                                                                                                                              The source specified that 1 state resident was impacted, which is the only numeric data provided. No total records impacted was specified, therefore the max records impacted is determined to be 1.
## 7                                                                                           Given that only one individual from the state of Indiana has been reported as impacted according to the source, and no further details on the total number of records have been provided, the maximum number of records impacted is determined to be 1.
##   Information.Types Encryption.Status
## 2        IDENTIFIER       UNENCRYPTED
## 3              UNKN              UNKN
## 4              UNKN              UNKN
## 5              UNKN              UNKN
## 6              UNKN       UNENCRYPTED
## 7              UNKN              UNKN
##                                                                                             Information.Types.Description
## 2                                                             Names and contact information were involved in the breach.}
## 3                             No specific information about the types of information exposed in the breach is provided.}]
## 4 The data breach notification provides no details regarding the types of information that were impacted in the breach.}]
## 5            The description does not provide any information regarding the types of information exposed in the breach.}]
## 6                               There is no information available about the types of information exposed in the breach.}]
## 7              There is no information provided regarding the types of information that were compromised in the breach.}]
##        State
## 2   Virginia
## 3    Indiana
## 4 New Jersey
## 5    Indiana
## 6      Maine
## 7    Indiana
##                                                                                                                                                                                                                                                                                       breach.location.explanation
## 2                                                                                                                    The location information is provided at the end of the data breach notification letter where Pat McClanahan, Vice President of People Operations at GANNETT, lists an address in McLean, VA.
## 3 The breach notification does not include specific location details such as street, city, state, or zip code. However, given the name 'Somerset County,' it can be inferred that the entity belongs to the United States, but without more information, we cannot specify the exact location within the country.
## 4                                                               The breach notification does not provide specific location details for Bed Bath & Beyond Inc. However, as of the knowledge cutoff in 2023, the company is known to have its corporate headquarters in Union, New Jersey, with the zip code 07068.
## 5                            The breach notification source indicates that the entity is located in Indiana, but no specific city, street address, or zip code is provided. Angola Wire Products Inc. does not have widely known location details as of 2023, which would allow us to specify the city or street.
## 6                                                                                                                                                 The breach notification does not include any specific location details such as street address, city, state, zip code, or country for State Industrial Products.
## 7                                                                                                                                                      Only one individual from the state of Indiana has been reported as impacted, but no specific address or location of the breach within Indiana is provided.
##                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                             Description.of.Breach
## 2 Gannett Company, Inc. experienced a phishing attack compromising the Office 365 and email account credentials of several HR department members on March 30, 2017. There was an attempt at a fraudulent wire transfer using these credentials. Personal data of employees and beneficiaries were potentially accessed, including contact information, SSNs, dates of birth, bank details, and employment-related information. Gannett alerted employees, locked down affected accounts, and provided credit monitoring services.
## 3                                                                                                                                                                                                                            The breach at Somerset County occurred on May 19, 2022, and was reported on September 28, 2022. There is no specific information given about the nature of the breach or the types of information compromised. The total records impacted as per the source is 14,599 and impacts 7 state residents.
## 4                                                                                                                                                                         Bed Bath & Beyond Inc. experienced a data breach between November 21, 2017, and December 8, 2017, which was discovered on February 6, 2018. The breach was reported on February 13, 2018. As there is no detailed information available in the notification, it is unclear what happened, who was impacted, and what specific information was affected.
## 5                                                                                                                                                                                                                                                         The breach occurred on January 5, 2023, and was reported on June 2, 2023. Specific details of the breach, including how it happened and the information impacted, are not provided. It has been publicly reported through a data breach notification source in Indiana.
## 6                                                                                                                                                                                                                                        The breach occurred at State Industrial Products between February 16, 2017, and June 25, 2017. It was reported on August 4, 2017. There is currently no information available regarding the nature of the breach, the number of records impacted or the type of information compromised.
## 7                                                                                                                                                                                                                                                                                   Details of the breach for CCS Technology Group LLC have not been provided, apart from the breach date and the reported date. Only one individual from the state of Indiana appears to have been recorded as impacted according to the source.
##   Reported.Date.New Date.of.Breach.New Year.of.Breach
## 2        2017-04-28         2017-03-29           2017
## 3        2022-09-28         2022-08-29           2022
## 4        2018-02-13         2017-12-08           2017
## 5        2023-06-02         2023-05-03           2023
## 6        2017-08-04         2017-06-25           2017
## 7        2017-01-18         2016-12-19           2016
write.csv(PRC2024_cleanV2, "Data/PRC2024_cleanV2.csv", row.names = FALSE)

Count how many NAs in dataset

# Check for NAs in the entire dataset
nas_in_dataset_clean <- colSums(is.na(PRC2024_cleanV2))

# Print the result
print(nas_in_dataset_clean)
##                            Index                   Name.of.Entity 
##                                0                                0 
##                           Source                Organization.Type 
##                                0                                0 
##    Organization.Type.Explanation                      Breach.Type 
##                                0                                0 
##                    Breach.Impute          Breach.Type.Explanation 
##                                0                                0 
##             Max.Records.Impacted Max.Records.Impacted.Explanation 
##                                0                                0 
##                Information.Types                Encryption.Status 
##                                0                                0 
##    Information.Types.Description                            State 
##                                0                                0 
##      breach.location.explanation            Description.of.Breach 
##                                0                                0 
##                Reported.Date.New               Date.of.Breach.New 
##                                0                                0 
##                   Year.of.Breach 
##                                0
summary(PRC2024_cleanV2)
##      Index       Name.of.Entity        Source          Organization.Type 
##  Min.   :    2   Length:25256       Length:25256       Length:25256      
##  1st Qu.: 8867   Class :character   Class :character   Class :character  
##  Median :17630   Mode  :character   Mode  :character   Mode  :character  
##  Mean   :17617                                                           
##  3rd Qu.:26386                                                           
##  Max.   :35166                                                           
##  Organization.Type.Explanation Breach.Type        Breach.Impute     
##  Length:25256                  Length:25256       Length:25256      
##  Class :character              Class :character   Class :character  
##  Mode  :character              Mode  :character   Mode  :character  
##                                                                     
##                                                                     
##                                                                     
##  Breach.Type.Explanation Max.Records.Impacted Max.Records.Impacted.Explanation
##  Length:25256            Min.   :0.000e+00    Length:25256                    
##  Class :character        1st Qu.:8.000e+00    Class :character                
##  Mode  :character        Median :5.020e+02    Mode  :character                
##                          Mean   :5.290e+05                                    
##                          3rd Qu.:3.383e+03                                    
##                          Max.   :2.000e+09                                    
##  Information.Types  Encryption.Status  Information.Types.Description
##  Length:25256       Length:25256       Length:25256                 
##  Class :character   Class :character   Class :character             
##  Mode  :character   Mode  :character   Mode  :character             
##                                                                     
##                                                                     
##                                                                     
##     State           breach.location.explanation Description.of.Breach
##  Length:25256       Length:25256                Length:25256         
##  Class :character   Class :character            Class :character     
##  Mode  :character   Mode  :character            Mode  :character     
##                                                                      
##                                                                      
##                                                                      
##  Reported.Date.New    Date.of.Breach.New   Year.of.Breach
##  Min.   :2006-11-10   Min.   :2006-10-11   Min.   :2006  
##  1st Qu.:2016-11-08   1st Qu.:2016-09-23   1st Qu.:2016  
##  Median :2018-09-28   Median :2018-07-31   Median :2018  
##  Mean   :2018-06-19   Mean   :2018-05-02   Mean   :2018  
##  3rd Qu.:2020-05-12   3rd Qu.:2020-03-28   3rd Qu.:2020  
##  Max.   :2023-09-28   Max.   :2023-12-08   Max.   :2023
str(PRC2024_cleanV2)
## 'data.frame':    25256 obs. of  19 variables:
##  $ Index                           : num  2 3 4 5 6 7 8 9 11 12 ...
##  $ Name.of.Entity                  : chr  "Gannett Company Inc." "Somerset County" "Bed Bath & Beyond Inc." "Angola Wire Products Inc." ...
##  $ Source                          : chr  "IA" "IN" "ME" "IN" ...
##  $ Organization.Type               : chr  "BSO" "GOV" "BSR" "BSO" ...
##  $ Organization.Type.Explanation   : chr  "Gannett Company, Inc. is primarily a media company involved in publishing industries, making it a BSO type of organization." "The name 'Somerset County' indicates it is a local government entity, as counties are typically administrative "| __truncated__ "Bed Bath & Beyond Inc. is a retail business, which falls into the category of Retail Businesses (BSR)." "Angola Wire Products Inc. appears to be a manufacturing company, which classifies it as a 'BSO' since it falls "| __truncated__ ...
##  $ Breach.Type                     : chr  "HACK" "UNKN" "UNKN" "UNKN" ...
##  $ Breach.Impute                   : chr  "HACK" "INSD" "CARD" "INSD" ...
##  $ Breach.Type.Explanation         : chr  "The breach was a result of a phishing attack targeting HR department members which led to the compromise of Off"| __truncated__ "The breach type is classified as 'UNKN' due to the lack of specific information given about the nature of the b"| __truncated__ "Insufficient details about the breach are provided. It is unclear whether it was a hacking incident, an interna"| __truncated__ "The data breach report does not provide specific information about how the breach occurred. The method and natu"| __truncated__ ...
##  $ Max.Records.Impacted            : num  18100 14599 1 1085 1 ...
##  $ Max.Records.Impacted.Explanation: chr  "The data breach notification letter states that approximately 18,100 individuals were affected by this incident"| __truncated__ "The maximum number of records impacted is determined to be 14,599, which is the total provided from the source."| __truncated__ "Although the total records impacted are not provided, the source states that there was 1 state record impacted."| __truncated__ "The source provided a total of 1,085 records impacted, and no specific count was provided in the data breach no"| __truncated__ ...
##  $ Information.Types               : chr  "IDENTIFIER" "UNKN" "UNKN" "UNKN" ...
##  $ Encryption.Status               : chr  "UNENCRYPTED" "UNKN" "UNKN" "UNKN" ...
##  $ Information.Types.Description   : chr  "Names and contact information were involved in the breach.}" "No specific information about the types of information exposed in the breach is provided.}]" "The data breach notification provides no details regarding the types of information that were impacted in the breach.}]" "The description does not provide any information regarding the types of information exposed in the breach.}]" ...
##  $ State                           : chr  "Virginia" "Indiana" "New Jersey" "Indiana" ...
##  $ breach.location.explanation     : chr  "The location information is provided at the end of the data breach notification letter where Pat McClanahan, Vi"| __truncated__ "The breach notification does not include specific location details such as street, city, state, or zip code. Ho"| __truncated__ "The breach notification does not provide specific location details for Bed Bath & Beyond Inc. However, as of th"| __truncated__ "The breach notification source indicates that the entity is located in Indiana, but no specific city, street ad"| __truncated__ ...
##  $ Description.of.Breach           : chr  "Gannett Company, Inc. experienced a phishing attack compromising the Office 365 and email account credentials o"| __truncated__ "The breach at Somerset County occurred on May 19, 2022, and was reported on September 28, 2022. There is no spe"| __truncated__ "Bed Bath & Beyond Inc. experienced a data breach between November 21, 2017, and December 8, 2017, which was dis"| __truncated__ "The breach occurred on January 5, 2023, and was reported on June 2, 2023. Specific details of the breach, inclu"| __truncated__ ...
##  $ Reported.Date.New               : Date, format: "2017-04-28" "2022-09-28" ...
##  $ Date.of.Breach.New              : Date, format: "2017-03-29" "2022-08-29" ...
##  $ Year.of.Breach                  : num  2017 2022 2017 2023 2017 ...
##  - attr(*, "na.action")= 'omit' Named int [1:9916] 1 10 16 17 23 27 28 29 31 32 ...
##   ..- attr(*, "names")= chr [1:9916] "1" "10" "16" "17" ...
PRC2024_cleanV2 <- PRC2024_cleanV2 %>% rename(Breach.Input = Breach.Impute)

Export to CSV

write.csv(PRC2024_cleanV2, "Data/PRC2024_CleanV2.csv", row.names = FALSE)

**END OF DATA CLEANING

Data Split

SPLITTING THE DATASET BEFORE MODEL CREATIONS

This split is for models that do not require the severity characteristic because it will not filter by MED organization

First, we are going to create the severity variable from the PRC Clean V2 dataset.

# Load necessary libraries
library(dplyr)
library(scales)

# Load existing dataset
# Assuming your dataset is in a CSV file named 'PRC2024_CleanV2.csv'
data <- read.csv('Data/PRC2024_CleanV2.csv')

# Normalize 'Max.Records.Impacted'
data <- data %>%
  mutate(normalized_records = rescale(Max.Records.Impacted))

# Identify unique values in 'Breach.Input' and 'Organization.Type'
unique_attacks <- unique(data$Breach.Input)
unique_industries <- unique(data$Organization.Type)

# Generate mappings for the unique values
attack_mapping <- setNames(seq(0.1, 1, length.out = length(unique_attacks)), unique_attacks)
industry_mapping <- setNames(seq(0.1, 1, length.out = length(unique_industries)), unique_industries)

# Map 'Breach.Input' and 'Organization.Type' to numerical values
data <- data %>%
  mutate(attack_score = attack_mapping[Breach.Input],
         industry_score = industry_mapping[Organization.Type])

# Calculate severity score
data <- data %>%
  mutate(severity_score = 0.5 * normalized_records +
                           0.3 * attack_score +
                           0.2 * industry_score)

# Function to create severity category based on given conditions
create_severity <- function(data) {
  data$severity_category <- with(data, ifelse(
    Max.Records.Impacted >= 1 & Max.Records.Impacted <= 499, "Below500",
    ifelse(Max.Records.Impacted >= 500 & Max.Records.Impacted <= 2000, "Low",
           ifelse(Max.Records.Impacted >= 2001 & Max.Records.Impacted <= 4000, "Med", "High")
    )))
  return(data)
}

# Apply the function to the dataset
data <- create_severity(data)

# View the dataset with the new columns
head(data)
##   Index            Name.of.Entity Source Organization.Type
## 1     2      Gannett Company Inc.     IA               BSO
## 2     3           Somerset County     IN               GOV
## 3     4    Bed Bath & Beyond Inc.     ME               BSR
## 4     5 Angola Wire Products Inc.     IN               BSO
## 5     6 State Industrial Products     ME               BSO
## 6     7  CCS Technology Group LLC     IN               BSO
##                                                                                                                                                                                                                                               Organization.Type.Explanation
## 1                                                                                                                                               Gannett Company, Inc. is primarily a media company involved in publishing industries, making it a BSO type of organization.
## 2                                                                                                                                  The name 'Somerset County' indicates it is a local government entity, as counties are typically administrative divisions within a state.
## 3                                                                                                                                                                    Bed Bath & Beyond Inc. is a retail business, which falls into the category of Retail Businesses (BSR).
## 4                                                                               Angola Wire Products Inc. appears to be a manufacturing company, which classifies it as a 'BSO' since it falls under the Other Businesses category which includes manufacturing industries.
## 5 State Industrial Products does not clearly fit into the categories of finance, retail, education, government, or healthcare. Given the name, it likely falls under 'Other Businesses' which could be related to manufacturing or providing industrial goods and services.
## 6                                                                                                                                      CCS Technology Group LLC seems to be an IT or technology services firm, which typically falls under the 'Other Businesses' category.
##   Breach.Type Breach.Input
## 1        HACK         HACK
## 2        UNKN         INSD
## 3        UNKN         CARD
## 4        UNKN         INSD
## 5        UNKN         INSD
## 6        UNKN         INSD
##                                                                                                                                                                                                                                                                                                                                                                                                                                        Breach.Type.Explanation
## 1                                                                                                                                                                                                                The breach was a result of a phishing attack targeting HR department members which led to the compromise of Office 365 and email account credentials. This falls under the definition of a cyberattack involving unauthorized network access.
## 2                                                                                                                                                                                                                    The breach type is classified as 'UNKN' due to the lack of specific information given about the nature of the breach. Since no details are provided about how the data was compromised, it is not possible to assign another breach type.
## 3                                                                                                                                                                                                                                                                                                 Insufficient details about the breach are provided. It is unclear whether it was a hacking incident, an internal mistake, physical theft, or something else.
## 4 The data breach report does not provide specific information about how the breach occurred. The method and nature of the data compromise are not mentioned, and there is no data breach notification letter available to elaborate on the incident's details. Therefore, without additional information indicating whether it was an insider action, hacking incident, physical document loss, or any other breach type, it has to be classified as unknown.
## 5                                                                                                                                                                                                                                                                      The nature of the breach, including the methods of data exposure and the specific details of how the breach occurred, were not provided, thus the breach type is classified as Unknown.
## 6                                                                                                                                                                                                                                                                                                          Details of the breach for CCS Technology Group LLC have not been provided, therefore it is impossible to appropriately classify the type of breach.
##   Max.Records.Impacted
## 1                18100
## 2                14599
## 3                    1
## 4                 1085
## 5                    1
## 6                    1
##                                                                                                                                                                                                                                                                                                                    Max.Records.Impacted.Explanation
## 1                                                                                                                                               The data breach notification letter states that approximately 18,100 individuals were affected by this incident, which is the specific and highest figure given for the number of records impacted.
## 2                                                                                                              The maximum number of records impacted is determined to be 14,599, which is the total provided from the source. No additional information has been provided from the data breach notification letter to indicate a different number.
## 3                                                                                                                                             Although the total records impacted are not provided, the source states that there was 1 state record impacted. This is the only specific figure provided, so it is used as the max records impacted.
## 4 The source provided a total of 1,085 records impacted, and no specific count was provided in the data breach notification letter. The information regarding state records stands at 647, which is the lower figure. Therefore, the highest number available and confirmed is the total records impacted provided from the source, which is 1,085.
## 5                                                                                                                                              The source specified that 1 state resident was impacted, which is the only numeric data provided. No total records impacted was specified, therefore the max records impacted is determined to be 1.
## 6                                                                                           Given that only one individual from the state of Indiana has been reported as impacted according to the source, and no further details on the total number of records have been provided, the maximum number of records impacted is determined to be 1.
##   Information.Types Encryption.Status
## 1        IDENTIFIER       UNENCRYPTED
## 2              UNKN              UNKN
## 3              UNKN              UNKN
## 4              UNKN              UNKN
## 5              UNKN       UNENCRYPTED
## 6              UNKN              UNKN
##                                                                                             Information.Types.Description
## 1                                                             Names and contact information were involved in the breach.}
## 2                             No specific information about the types of information exposed in the breach is provided.}]
## 3 The data breach notification provides no details regarding the types of information that were impacted in the breach.}]
## 4            The description does not provide any information regarding the types of information exposed in the breach.}]
## 5                               There is no information available about the types of information exposed in the breach.}]
## 6              There is no information provided regarding the types of information that were compromised in the breach.}]
##        State
## 1   Virginia
## 2    Indiana
## 3 New Jersey
## 4    Indiana
## 5      Maine
## 6    Indiana
##                                                                                                                                                                                                                                                                                       breach.location.explanation
## 1                                                                                                                    The location information is provided at the end of the data breach notification letter where Pat McClanahan, Vice President of People Operations at GANNETT, lists an address in McLean, VA.
## 2 The breach notification does not include specific location details such as street, city, state, or zip code. However, given the name 'Somerset County,' it can be inferred that the entity belongs to the United States, but without more information, we cannot specify the exact location within the country.
## 3                                                               The breach notification does not provide specific location details for Bed Bath & Beyond Inc. However, as of the knowledge cutoff in 2023, the company is known to have its corporate headquarters in Union, New Jersey, with the zip code 07068.
## 4                            The breach notification source indicates that the entity is located in Indiana, but no specific city, street address, or zip code is provided. Angola Wire Products Inc. does not have widely known location details as of 2023, which would allow us to specify the city or street.
## 5                                                                                                                                                 The breach notification does not include any specific location details such as street address, city, state, zip code, or country for State Industrial Products.
## 6                                                                                                                                                      Only one individual from the state of Indiana has been reported as impacted, but no specific address or location of the breach within Indiana is provided.
##                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                             Description.of.Breach
## 1 Gannett Company, Inc. experienced a phishing attack compromising the Office 365 and email account credentials of several HR department members on March 30, 2017. There was an attempt at a fraudulent wire transfer using these credentials. Personal data of employees and beneficiaries were potentially accessed, including contact information, SSNs, dates of birth, bank details, and employment-related information. Gannett alerted employees, locked down affected accounts, and provided credit monitoring services.
## 2                                                                                                                                                                                                                            The breach at Somerset County occurred on May 19, 2022, and was reported on September 28, 2022. There is no specific information given about the nature of the breach or the types of information compromised. The total records impacted as per the source is 14,599 and impacts 7 state residents.
## 3                                                                                                                                                                         Bed Bath & Beyond Inc. experienced a data breach between November 21, 2017, and December 8, 2017, which was discovered on February 6, 2018. The breach was reported on February 13, 2018. As there is no detailed information available in the notification, it is unclear what happened, who was impacted, and what specific information was affected.
## 4                                                                                                                                                                                                                                                         The breach occurred on January 5, 2023, and was reported on June 2, 2023. Specific details of the breach, including how it happened and the information impacted, are not provided. It has been publicly reported through a data breach notification source in Indiana.
## 5                                                                                                                                                                                                                                        The breach occurred at State Industrial Products between February 16, 2017, and June 25, 2017. It was reported on August 4, 2017. There is currently no information available regarding the nature of the breach, the number of records impacted or the type of information compromised.
## 6                                                                                                                                                                                                                                                                                   Details of the breach for CCS Technology Group LLC have not been provided, apart from the breach date and the reported date. Only one individual from the state of Indiana appears to have been recorded as impacted according to the source.
##   Reported.Date.New Date.of.Breach.New Year.of.Breach normalized_records
## 1        2017-04-28         2017-03-29           2017         9.0500e-06
## 2        2022-09-28         2022-08-29           2022         7.2995e-06
## 3        2018-02-13         2017-12-08           2017         5.0000e-10
## 4        2023-06-02         2023-05-03           2023         5.4250e-07
## 5        2017-08-04         2017-06-25           2017         5.0000e-10
## 6        2017-01-18         2016-12-19           2016         5.0000e-10
##   attack_score industry_score severity_score severity_category
## 1         0.10      0.1000000     0.05000453              High
## 2         0.25      0.2285714     0.12071794              High
## 3         0.40      0.3571429     0.19142857          Below500
## 4         0.25      0.1000000     0.09500027               Low
## 5         0.25      0.1000000     0.09500000          Below500
## 6         0.25      0.1000000     0.09500000          Below500

Save the new dataset as ‘prc_with_severity.csv’

# Save the new dataset as 'prc_with_severity.csv'
write.csv(data, 'Data/prc_with_severity.csv', row.names = FALSE)
prc_with_severity <- read.csv('Data/prc_with_severity.csv')
head(prc_with_severity)
##   Index            Name.of.Entity Source Organization.Type
## 1     2      Gannett Company Inc.     IA               BSO
## 2     3           Somerset County     IN               GOV
## 3     4    Bed Bath & Beyond Inc.     ME               BSR
## 4     5 Angola Wire Products Inc.     IN               BSO
## 5     6 State Industrial Products     ME               BSO
## 6     7  CCS Technology Group LLC     IN               BSO
##                                                                                                                                                                                                                                               Organization.Type.Explanation
## 1                                                                                                                                               Gannett Company, Inc. is primarily a media company involved in publishing industries, making it a BSO type of organization.
## 2                                                                                                                                  The name 'Somerset County' indicates it is a local government entity, as counties are typically administrative divisions within a state.
## 3                                                                                                                                                                    Bed Bath & Beyond Inc. is a retail business, which falls into the category of Retail Businesses (BSR).
## 4                                                                               Angola Wire Products Inc. appears to be a manufacturing company, which classifies it as a 'BSO' since it falls under the Other Businesses category which includes manufacturing industries.
## 5 State Industrial Products does not clearly fit into the categories of finance, retail, education, government, or healthcare. Given the name, it likely falls under 'Other Businesses' which could be related to manufacturing or providing industrial goods and services.
## 6                                                                                                                                      CCS Technology Group LLC seems to be an IT or technology services firm, which typically falls under the 'Other Businesses' category.
##   Breach.Type Breach.Input
## 1        HACK         HACK
## 2        UNKN         INSD
## 3        UNKN         CARD
## 4        UNKN         INSD
## 5        UNKN         INSD
## 6        UNKN         INSD
##                                                                                                                                                                                                                                                                                                                                                                                                                                        Breach.Type.Explanation
## 1                                                                                                                                                                                                                The breach was a result of a phishing attack targeting HR department members which led to the compromise of Office 365 and email account credentials. This falls under the definition of a cyberattack involving unauthorized network access.
## 2                                                                                                                                                                                                                    The breach type is classified as 'UNKN' due to the lack of specific information given about the nature of the breach. Since no details are provided about how the data was compromised, it is not possible to assign another breach type.
## 3                                                                                                                                                                                                                                                                                                 Insufficient details about the breach are provided. It is unclear whether it was a hacking incident, an internal mistake, physical theft, or something else.
## 4 The data breach report does not provide specific information about how the breach occurred. The method and nature of the data compromise are not mentioned, and there is no data breach notification letter available to elaborate on the incident's details. Therefore, without additional information indicating whether it was an insider action, hacking incident, physical document loss, or any other breach type, it has to be classified as unknown.
## 5                                                                                                                                                                                                                                                                      The nature of the breach, including the methods of data exposure and the specific details of how the breach occurred, were not provided, thus the breach type is classified as Unknown.
## 6                                                                                                                                                                                                                                                                                                          Details of the breach for CCS Technology Group LLC have not been provided, therefore it is impossible to appropriately classify the type of breach.
##   Max.Records.Impacted
## 1                18100
## 2                14599
## 3                    1
## 4                 1085
## 5                    1
## 6                    1
##                                                                                                                                                                                                                                                                                                                    Max.Records.Impacted.Explanation
## 1                                                                                                                                               The data breach notification letter states that approximately 18,100 individuals were affected by this incident, which is the specific and highest figure given for the number of records impacted.
## 2                                                                                                              The maximum number of records impacted is determined to be 14,599, which is the total provided from the source. No additional information has been provided from the data breach notification letter to indicate a different number.
## 3                                                                                                                                             Although the total records impacted are not provided, the source states that there was 1 state record impacted. This is the only specific figure provided, so it is used as the max records impacted.
## 4 The source provided a total of 1,085 records impacted, and no specific count was provided in the data breach notification letter. The information regarding state records stands at 647, which is the lower figure. Therefore, the highest number available and confirmed is the total records impacted provided from the source, which is 1,085.
## 5                                                                                                                                              The source specified that 1 state resident was impacted, which is the only numeric data provided. No total records impacted was specified, therefore the max records impacted is determined to be 1.
## 6                                                                                           Given that only one individual from the state of Indiana has been reported as impacted according to the source, and no further details on the total number of records have been provided, the maximum number of records impacted is determined to be 1.
##   Information.Types Encryption.Status
## 1        IDENTIFIER       UNENCRYPTED
## 2              UNKN              UNKN
## 3              UNKN              UNKN
## 4              UNKN              UNKN
## 5              UNKN       UNENCRYPTED
## 6              UNKN              UNKN
##                                                                                             Information.Types.Description
## 1                                                             Names and contact information were involved in the breach.}
## 2                             No specific information about the types of information exposed in the breach is provided.}]
## 3 The data breach notification provides no details regarding the types of information that were impacted in the breach.}]
## 4            The description does not provide any information regarding the types of information exposed in the breach.}]
## 5                               There is no information available about the types of information exposed in the breach.}]
## 6              There is no information provided regarding the types of information that were compromised in the breach.}]
##        State
## 1   Virginia
## 2    Indiana
## 3 New Jersey
## 4    Indiana
## 5      Maine
## 6    Indiana
##                                                                                                                                                                                                                                                                                       breach.location.explanation
## 1                                                                                                                    The location information is provided at the end of the data breach notification letter where Pat McClanahan, Vice President of People Operations at GANNETT, lists an address in McLean, VA.
## 2 The breach notification does not include specific location details such as street, city, state, or zip code. However, given the name 'Somerset County,' it can be inferred that the entity belongs to the United States, but without more information, we cannot specify the exact location within the country.
## 3                                                               The breach notification does not provide specific location details for Bed Bath & Beyond Inc. However, as of the knowledge cutoff in 2023, the company is known to have its corporate headquarters in Union, New Jersey, with the zip code 07068.
## 4                            The breach notification source indicates that the entity is located in Indiana, but no specific city, street address, or zip code is provided. Angola Wire Products Inc. does not have widely known location details as of 2023, which would allow us to specify the city or street.
## 5                                                                                                                                                 The breach notification does not include any specific location details such as street address, city, state, zip code, or country for State Industrial Products.
## 6                                                                                                                                                      Only one individual from the state of Indiana has been reported as impacted, but no specific address or location of the breach within Indiana is provided.
##                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                             Description.of.Breach
## 1 Gannett Company, Inc. experienced a phishing attack compromising the Office 365 and email account credentials of several HR department members on March 30, 2017. There was an attempt at a fraudulent wire transfer using these credentials. Personal data of employees and beneficiaries were potentially accessed, including contact information, SSNs, dates of birth, bank details, and employment-related information. Gannett alerted employees, locked down affected accounts, and provided credit monitoring services.
## 2                                                                                                                                                                                                                            The breach at Somerset County occurred on May 19, 2022, and was reported on September 28, 2022. There is no specific information given about the nature of the breach or the types of information compromised. The total records impacted as per the source is 14,599 and impacts 7 state residents.
## 3                                                                                                                                                                         Bed Bath & Beyond Inc. experienced a data breach between November 21, 2017, and December 8, 2017, which was discovered on February 6, 2018. The breach was reported on February 13, 2018. As there is no detailed information available in the notification, it is unclear what happened, who was impacted, and what specific information was affected.
## 4                                                                                                                                                                                                                                                         The breach occurred on January 5, 2023, and was reported on June 2, 2023. Specific details of the breach, including how it happened and the information impacted, are not provided. It has been publicly reported through a data breach notification source in Indiana.
## 5                                                                                                                                                                                                                                        The breach occurred at State Industrial Products between February 16, 2017, and June 25, 2017. It was reported on August 4, 2017. There is currently no information available regarding the nature of the breach, the number of records impacted or the type of information compromised.
## 6                                                                                                                                                                                                                                                                                   Details of the breach for CCS Technology Group LLC have not been provided, apart from the breach date and the reported date. Only one individual from the state of Indiana appears to have been recorded as impacted according to the source.
##   Reported.Date.New Date.of.Breach.New Year.of.Breach normalized_records
## 1        2017-04-28         2017-03-29           2017         9.0500e-06
## 2        2022-09-28         2022-08-29           2022         7.2995e-06
## 3        2018-02-13         2017-12-08           2017         5.0000e-10
## 4        2023-06-02         2023-05-03           2023         5.4250e-07
## 5        2017-08-04         2017-06-25           2017         5.0000e-10
## 6        2017-01-18         2016-12-19           2016         5.0000e-10
##   attack_score industry_score severity_score severity_category
## 1         0.10      0.1000000     0.05000453              High
## 2         0.25      0.2285714     0.12071794              High
## 3         0.40      0.3571429     0.19142857          Below500
## 4         0.25      0.1000000     0.09500027               Low
## 5         0.25      0.1000000     0.09500000          Below500
## 6         0.25      0.1000000     0.09500000          Below500
summary(prc_with_severity)
##      Index       Name.of.Entity        Source          Organization.Type 
##  Min.   :    2   Length:25256       Length:25256       Length:25256      
##  1st Qu.: 8867   Class :character   Class :character   Class :character  
##  Median :17630   Mode  :character   Mode  :character   Mode  :character  
##  Mean   :17617                                                           
##  3rd Qu.:26386                                                           
##  Max.   :35166                                                           
##  Organization.Type.Explanation Breach.Type        Breach.Input      
##  Length:25256                  Length:25256       Length:25256      
##  Class :character              Class :character   Class :character  
##  Mode  :character              Mode  :character   Mode  :character  
##                                                                     
##                                                                     
##                                                                     
##  Breach.Type.Explanation Max.Records.Impacted Max.Records.Impacted.Explanation
##  Length:25256            Min.   :0.000e+00    Length:25256                    
##  Class :character        1st Qu.:8.000e+00    Class :character                
##  Mode  :character        Median :5.020e+02    Mode  :character                
##                          Mean   :5.290e+05                                    
##                          3rd Qu.:3.383e+03                                    
##                          Max.   :2.000e+09                                    
##  Information.Types  Encryption.Status  Information.Types.Description
##  Length:25256       Length:25256       Length:25256                 
##  Class :character   Class :character   Class :character             
##  Mode  :character   Mode  :character   Mode  :character             
##                                                                     
##                                                                     
##                                                                     
##     State           breach.location.explanation Description.of.Breach
##  Length:25256       Length:25256                Length:25256         
##  Class :character   Class :character            Class :character     
##  Mode  :character   Mode  :character            Mode  :character     
##                                                                      
##                                                                      
##                                                                      
##  Reported.Date.New  Date.of.Breach.New Year.of.Breach normalized_records 
##  Length:25256       Length:25256       Min.   :2006   Min.   :0.000e+00  
##  Class :character   Class :character   1st Qu.:2016   1st Qu.:0.000e+00  
##  Mode  :character   Mode  :character   Median :2018   Median :2.500e-07  
##                                        Mean   :2018   Mean   :2.645e-04  
##                                        3rd Qu.:2020   3rd Qu.:1.690e-06  
##                                        Max.   :2023   Max.   :1.000e+00  
##   attack_score    industry_score   severity_score   severity_category 
##  Min.   :0.1000   Min.   :0.1000   Min.   :0.0500   Length:25256      
##  1st Qu.:0.1000   1st Qu.:0.1000   1st Qu.:0.0950   Class :character  
##  Median :0.2500   Median :0.3571   Median :0.1465   Mode  :character  
##  Mean   :0.2696   Mean   :0.3935   Mean   :0.1597                     
##  3rd Qu.:0.2500   3rd Qu.:0.6143   3rd Qu.:0.1979                     
##  Max.   :1.0000   Max.   :1.0000   Max.   :0.6014
str(prc_with_severity)
## 'data.frame':    25256 obs. of  24 variables:
##  $ Index                           : int  2 3 4 5 6 7 8 9 11 12 ...
##  $ Name.of.Entity                  : chr  "Gannett Company Inc." "Somerset County" "Bed Bath & Beyond Inc." "Angola Wire Products Inc." ...
##  $ Source                          : chr  "IA" "IN" "ME" "IN" ...
##  $ Organization.Type               : chr  "BSO" "GOV" "BSR" "BSO" ...
##  $ Organization.Type.Explanation   : chr  "Gannett Company, Inc. is primarily a media company involved in publishing industries, making it a BSO type of organization." "The name 'Somerset County' indicates it is a local government entity, as counties are typically administrative "| __truncated__ "Bed Bath & Beyond Inc. is a retail business, which falls into the category of Retail Businesses (BSR)." "Angola Wire Products Inc. appears to be a manufacturing company, which classifies it as a 'BSO' since it falls "| __truncated__ ...
##  $ Breach.Type                     : chr  "HACK" "UNKN" "UNKN" "UNKN" ...
##  $ Breach.Input                    : chr  "HACK" "INSD" "CARD" "INSD" ...
##  $ Breach.Type.Explanation         : chr  "The breach was a result of a phishing attack targeting HR department members which led to the compromise of Off"| __truncated__ "The breach type is classified as 'UNKN' due to the lack of specific information given about the nature of the b"| __truncated__ "Insufficient details about the breach are provided. It is unclear whether it was a hacking incident, an interna"| __truncated__ "The data breach report does not provide specific information about how the breach occurred. The method and natu"| __truncated__ ...
##  $ Max.Records.Impacted            : num  18100 14599 1 1085 1 ...
##  $ Max.Records.Impacted.Explanation: chr  "The data breach notification letter states that approximately 18,100 individuals were affected by this incident"| __truncated__ "The maximum number of records impacted is determined to be 14,599, which is the total provided from the source."| __truncated__ "Although the total records impacted are not provided, the source states that there was 1 state record impacted."| __truncated__ "The source provided a total of 1,085 records impacted, and no specific count was provided in the data breach no"| __truncated__ ...
##  $ Information.Types               : chr  "IDENTIFIER" "UNKN" "UNKN" "UNKN" ...
##  $ Encryption.Status               : chr  "UNENCRYPTED" "UNKN" "UNKN" "UNKN" ...
##  $ Information.Types.Description   : chr  "Names and contact information were involved in the breach.}" "No specific information about the types of information exposed in the breach is provided.}]" "The data breach notification provides no details regarding the types of information that were impacted in the breach.}]" "The description does not provide any information regarding the types of information exposed in the breach.}]" ...
##  $ State                           : chr  "Virginia" "Indiana" "New Jersey" "Indiana" ...
##  $ breach.location.explanation     : chr  "The location information is provided at the end of the data breach notification letter where Pat McClanahan, Vi"| __truncated__ "The breach notification does not include specific location details such as street, city, state, or zip code. Ho"| __truncated__ "The breach notification does not provide specific location details for Bed Bath & Beyond Inc. However, as of th"| __truncated__ "The breach notification source indicates that the entity is located in Indiana, but no specific city, street ad"| __truncated__ ...
##  $ Description.of.Breach           : chr  "Gannett Company, Inc. experienced a phishing attack compromising the Office 365 and email account credentials o"| __truncated__ "The breach at Somerset County occurred on May 19, 2022, and was reported on September 28, 2022. There is no spe"| __truncated__ "Bed Bath & Beyond Inc. experienced a data breach between November 21, 2017, and December 8, 2017, which was dis"| __truncated__ "The breach occurred on January 5, 2023, and was reported on June 2, 2023. Specific details of the breach, inclu"| __truncated__ ...
##  $ Reported.Date.New               : chr  "2017-04-28" "2022-09-28" "2018-02-13" "2023-06-02" ...
##  $ Date.of.Breach.New              : chr  "2017-03-29" "2022-08-29" "2017-12-08" "2023-05-03" ...
##  $ Year.of.Breach                  : int  2017 2022 2017 2023 2017 2016 2013 2018 2020 2017 ...
##  $ normalized_records              : num  9.05e-06 7.30e-06 5.00e-10 5.42e-07 5.00e-10 ...
##  $ attack_score                    : num  0.1 0.25 0.4 0.25 0.25 0.25 0.25 0.25 0.1 0.1 ...
##  $ industry_score                  : num  0.1 0.229 0.357 0.1 0.1 ...
##  $ severity_score                  : num  0.05 0.121 0.191 0.095 0.095 ...
##  $ severity_category               : chr  "High" "High" "Below500" "Low" ...
columns_with_NAs <- colnames(prc_with_severity)[colSums(is.na(prc_with_severity)) > 0]
print(columns_with_NAs)
## character(0)
# Load necessary libraries
library(dplyr)

# Assuming the prc_with_severity dataset is already loaded
# Example: prc_with_severity <- read.csv('prc_with_severity.csv')

# Create severity_score_categorical based on the ranges of severity_score
prc_with_severity <- prc_with_severity %>%
  mutate(severity_score_categorical = case_when(
    severity_score < 0.25 ~ "Low",
    severity_score >= 0.25 & severity_score <= 0.5 ~ "Med",
    severity_score > 0.5 & severity_score <= 0.75 ~ "High",
    severity_score > 0.75 ~ "Very High"
  ))

# Check the resulting dataset to ensure the new column was added correctly
head(prc_with_severity)
##   Index            Name.of.Entity Source Organization.Type
## 1     2      Gannett Company Inc.     IA               BSO
## 2     3           Somerset County     IN               GOV
## 3     4    Bed Bath & Beyond Inc.     ME               BSR
## 4     5 Angola Wire Products Inc.     IN               BSO
## 5     6 State Industrial Products     ME               BSO
## 6     7  CCS Technology Group LLC     IN               BSO
##                                                                                                                                                                                                                                               Organization.Type.Explanation
## 1                                                                                                                                               Gannett Company, Inc. is primarily a media company involved in publishing industries, making it a BSO type of organization.
## 2                                                                                                                                  The name 'Somerset County' indicates it is a local government entity, as counties are typically administrative divisions within a state.
## 3                                                                                                                                                                    Bed Bath & Beyond Inc. is a retail business, which falls into the category of Retail Businesses (BSR).
## 4                                                                               Angola Wire Products Inc. appears to be a manufacturing company, which classifies it as a 'BSO' since it falls under the Other Businesses category which includes manufacturing industries.
## 5 State Industrial Products does not clearly fit into the categories of finance, retail, education, government, or healthcare. Given the name, it likely falls under 'Other Businesses' which could be related to manufacturing or providing industrial goods and services.
## 6                                                                                                                                      CCS Technology Group LLC seems to be an IT or technology services firm, which typically falls under the 'Other Businesses' category.
##   Breach.Type Breach.Input
## 1        HACK         HACK
## 2        UNKN         INSD
## 3        UNKN         CARD
## 4        UNKN         INSD
## 5        UNKN         INSD
## 6        UNKN         INSD
##                                                                                                                                                                                                                                                                                                                                                                                                                                        Breach.Type.Explanation
## 1                                                                                                                                                                                                                The breach was a result of a phishing attack targeting HR department members which led to the compromise of Office 365 and email account credentials. This falls under the definition of a cyberattack involving unauthorized network access.
## 2                                                                                                                                                                                                                    The breach type is classified as 'UNKN' due to the lack of specific information given about the nature of the breach. Since no details are provided about how the data was compromised, it is not possible to assign another breach type.
## 3                                                                                                                                                                                                                                                                                                 Insufficient details about the breach are provided. It is unclear whether it was a hacking incident, an internal mistake, physical theft, or something else.
## 4 The data breach report does not provide specific information about how the breach occurred. The method and nature of the data compromise are not mentioned, and there is no data breach notification letter available to elaborate on the incident's details. Therefore, without additional information indicating whether it was an insider action, hacking incident, physical document loss, or any other breach type, it has to be classified as unknown.
## 5                                                                                                                                                                                                                                                                      The nature of the breach, including the methods of data exposure and the specific details of how the breach occurred, were not provided, thus the breach type is classified as Unknown.
## 6                                                                                                                                                                                                                                                                                                          Details of the breach for CCS Technology Group LLC have not been provided, therefore it is impossible to appropriately classify the type of breach.
##   Max.Records.Impacted
## 1                18100
## 2                14599
## 3                    1
## 4                 1085
## 5                    1
## 6                    1
##                                                                                                                                                                                                                                                                                                                    Max.Records.Impacted.Explanation
## 1                                                                                                                                               The data breach notification letter states that approximately 18,100 individuals were affected by this incident, which is the specific and highest figure given for the number of records impacted.
## 2                                                                                                              The maximum number of records impacted is determined to be 14,599, which is the total provided from the source. No additional information has been provided from the data breach notification letter to indicate a different number.
## 3                                                                                                                                             Although the total records impacted are not provided, the source states that there was 1 state record impacted. This is the only specific figure provided, so it is used as the max records impacted.
## 4 The source provided a total of 1,085 records impacted, and no specific count was provided in the data breach notification letter. The information regarding state records stands at 647, which is the lower figure. Therefore, the highest number available and confirmed is the total records impacted provided from the source, which is 1,085.
## 5                                                                                                                                              The source specified that 1 state resident was impacted, which is the only numeric data provided. No total records impacted was specified, therefore the max records impacted is determined to be 1.
## 6                                                                                           Given that only one individual from the state of Indiana has been reported as impacted according to the source, and no further details on the total number of records have been provided, the maximum number of records impacted is determined to be 1.
##   Information.Types Encryption.Status
## 1        IDENTIFIER       UNENCRYPTED
## 2              UNKN              UNKN
## 3              UNKN              UNKN
## 4              UNKN              UNKN
## 5              UNKN       UNENCRYPTED
## 6              UNKN              UNKN
##                                                                                             Information.Types.Description
## 1                                                             Names and contact information were involved in the breach.}
## 2                             No specific information about the types of information exposed in the breach is provided.}]
## 3 The data breach notification provides no details regarding the types of information that were impacted in the breach.}]
## 4            The description does not provide any information regarding the types of information exposed in the breach.}]
## 5                               There is no information available about the types of information exposed in the breach.}]
## 6              There is no information provided regarding the types of information that were compromised in the breach.}]
##        State
## 1   Virginia
## 2    Indiana
## 3 New Jersey
## 4    Indiana
## 5      Maine
## 6    Indiana
##                                                                                                                                                                                                                                                                                       breach.location.explanation
## 1                                                                                                                    The location information is provided at the end of the data breach notification letter where Pat McClanahan, Vice President of People Operations at GANNETT, lists an address in McLean, VA.
## 2 The breach notification does not include specific location details such as street, city, state, or zip code. However, given the name 'Somerset County,' it can be inferred that the entity belongs to the United States, but without more information, we cannot specify the exact location within the country.
## 3                                                               The breach notification does not provide specific location details for Bed Bath & Beyond Inc. However, as of the knowledge cutoff in 2023, the company is known to have its corporate headquarters in Union, New Jersey, with the zip code 07068.
## 4                            The breach notification source indicates that the entity is located in Indiana, but no specific city, street address, or zip code is provided. Angola Wire Products Inc. does not have widely known location details as of 2023, which would allow us to specify the city or street.
## 5                                                                                                                                                 The breach notification does not include any specific location details such as street address, city, state, zip code, or country for State Industrial Products.
## 6                                                                                                                                                      Only one individual from the state of Indiana has been reported as impacted, but no specific address or location of the breach within Indiana is provided.
##                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                             Description.of.Breach
## 1 Gannett Company, Inc. experienced a phishing attack compromising the Office 365 and email account credentials of several HR department members on March 30, 2017. There was an attempt at a fraudulent wire transfer using these credentials. Personal data of employees and beneficiaries were potentially accessed, including contact information, SSNs, dates of birth, bank details, and employment-related information. Gannett alerted employees, locked down affected accounts, and provided credit monitoring services.
## 2                                                                                                                                                                                                                            The breach at Somerset County occurred on May 19, 2022, and was reported on September 28, 2022. There is no specific information given about the nature of the breach or the types of information compromised. The total records impacted as per the source is 14,599 and impacts 7 state residents.
## 3                                                                                                                                                                         Bed Bath & Beyond Inc. experienced a data breach between November 21, 2017, and December 8, 2017, which was discovered on February 6, 2018. The breach was reported on February 13, 2018. As there is no detailed information available in the notification, it is unclear what happened, who was impacted, and what specific information was affected.
## 4                                                                                                                                                                                                                                                         The breach occurred on January 5, 2023, and was reported on June 2, 2023. Specific details of the breach, including how it happened and the information impacted, are not provided. It has been publicly reported through a data breach notification source in Indiana.
## 5                                                                                                                                                                                                                                        The breach occurred at State Industrial Products between February 16, 2017, and June 25, 2017. It was reported on August 4, 2017. There is currently no information available regarding the nature of the breach, the number of records impacted or the type of information compromised.
## 6                                                                                                                                                                                                                                                                                   Details of the breach for CCS Technology Group LLC have not been provided, apart from the breach date and the reported date. Only one individual from the state of Indiana appears to have been recorded as impacted according to the source.
##   Reported.Date.New Date.of.Breach.New Year.of.Breach normalized_records
## 1        2017-04-28         2017-03-29           2017         9.0500e-06
## 2        2022-09-28         2022-08-29           2022         7.2995e-06
## 3        2018-02-13         2017-12-08           2017         5.0000e-10
## 4        2023-06-02         2023-05-03           2023         5.4250e-07
## 5        2017-08-04         2017-06-25           2017         5.0000e-10
## 6        2017-01-18         2016-12-19           2016         5.0000e-10
##   attack_score industry_score severity_score severity_category
## 1         0.10      0.1000000     0.05000453              High
## 2         0.25      0.2285714     0.12071794              High
## 3         0.40      0.3571429     0.19142857          Below500
## 4         0.25      0.1000000     0.09500027               Low
## 5         0.25      0.1000000     0.09500000          Below500
## 6         0.25      0.1000000     0.09500000          Below500
##   severity_score_categorical
## 1                        Low
## 2                        Low
## 3                        Low
## 4                        Low
## 5                        Low
## 6                        Low
# Save the new dataset as 'prc_with_severity.csv'
write.csv(data, 'Data/prc_with_severity.csv', row.names = FALSE)

Now lets split the new dataset to be used in the below models

# Load necessary libraries
library(dplyr)

# Assuming the prc_with_severity dataset is already loaded
# Example: prc_with_severity <- read.csv('prc_with_severity.csv')

# Create severity_score_categorical based on the ranges of severity_score
prc_with_severity <- prc_with_severity %>%
  mutate(severity_score_categorical = case_when(
    severity_score < 0.25 ~ "Low",
    severity_score >= 0.25 & severity_score <= 0.5 ~ "Med",
    severity_score > 0.5 & severity_score <= 0.75 ~ "High",
    severity_score > 0.75 ~ "Very High"
  ))

# Set seed for reproducibility
set.seed(123)

# Calculate number of data points in the dataset
n <- nrow(prc_with_severity)
n_train <- floor(0.70 * n)
n_val <- floor(0.10 * n)
n_test <- n - n_train - n_val  # This ensures all data is used

# Create a random permutation of indices
indices <- sample(seq_len(n))

# Assign indices to each dataset
train_indices <- indices[seq_len(n_train)]
val_indices <- indices[(n_train + 1):(n_train + n_val)]
test_indices <- indices[(n_train + n_val + 1):n]

# Split the data
train_data_sev <- prc_with_severity[train_indices, ]
val_data_sev <- prc_with_severity[val_indices, ]
test_data_sev <- prc_with_severity[test_indices, ]

# Save the split datasets
write.csv(train_data_sev, 'Data/train_data_sev.csv', row.names = FALSE)
write.csv(val_data_sev, 'Data/val_data_sev.csv', row.names = FALSE)
write.csv(test_data_sev, 'Data/test_data_sev.csv', row.names = FALSE)

# view dataset to verify
head(train_data_sev)
##       Index                      Name.of.Entity Source Organization.Type
## 18847 26254 The Institute for Financial Markets     MD               BSF
## 18895 26317                   Julep Beauty Inc.     IN               BSR
## 25102 34954                        GoHenry Ltd.     IN               BSF
## 2986   4202             Tyler Technologies Inc.     MD               BSO
## 1842   2605                   LPL Financial LLC     NH               BSF
## 3371   4762     Rural Health Access Corporation    HHS               MED
##                                                                                                                                                                                    Organization.Type.Explanation
## 18847                                                                           The name of the entity suggests it is involved in the financial industry, which classifies it under Financial Services Business.
## 18895                                                 Julep Beauty Inc. likely falls under Retail Businesses as a beauty product company, which implies selling products to consumers either in-store or online.
## 25102 GoHenry Ltd. appears to be a financial service targeted towards families, likely involving financial transactions for children's allowances or savings, categorizing it under Financial Services Business.
## 2986                            Tyler Technologies Inc. provides integrated software and technology services to the public sector, making them an other business type under information and technology services.
## 1842                                                        The name 'LPL Financial LLC' suggests that the entity is involved in financial services, which classifies it as a Financial Services Business (BSF).
## 3371     The entity name and description indicate that it is a healthcare provider dealing with protected health information. Additionally, it was reported to HHS, which suggests it is a HIPAA covered entity.
##       Breach.Type Breach.Input
## 18847        HACK         HACK
## 18895        UNKN         HACK
## 25102        UNKN         INSD
## 2986         DISC         DISC
## 1842         HACK         HACK
## 3371         DISC         DISC
##                                                                                                                                                                                                                                                                                   Breach.Type.Explanation
## 18847                                                                                               The breach was identified as being due to malicious code from an IP address in the Russian Federation, classifying it as a cyberattack where unauthorized network access and system hacking occurred.
## 18895                                                                                             The description of the breach does not provide enough details to accurately classify the type of breach. It only states that there is limited information available and that 606 records were affected.
## 25102                                                                                            The specifics of the breach, including the method of compromise, have not been disclosed. Due to the lack of details on how the data breach occurred, the breach type can only be classified as Unknown.
## 2986  The breach type is classified as DISC because an employee of Tyler Technologies Inc. accidentally sent an email containing personal information to unauthorized recipients. This constitutes an unintended disclosure of personal data without any indication of malicious intent or physical loss.
## 1842                                              The breach involved a third-party system error that allowed unauthorized access to personal information. This incident fits the definition of HACK, as it includes unauthorized access to a system, likely through a network or software vulnerability.
## 3371           The breach involved an unintended disclosure of protected health information (PHI) that was shared un-deidentified with an organization without a business associate agreement. Since this disclosure was accidental and not related to hacking, loss, or theft, it is classified as DISC.
##       Max.Records.Impacted
## 18847                  248
## 18895                  606
## 25102                 5356
## 2986                     2
## 1842                    19
## 3371                   874
##                                                                                                                                                                                                                                                                                                        Max.Records.Impacted.Explanation
## 18847                                                                               The breach notification letter indicates that credit card details belonging to 248 individuals were exposed. No specific information is provided on the number of state residents impacted, and no additional data from other sources is available.
## 18895                                                                                                                                 The source provided a count of 606 total records impacted, which is the highest and only specific figure available since the data breach notification letter does not provide additional details.
## 25102                                                                                                     The source provided the total number of records impacted, which is 5,356. Since there is no conflicting information from the data breach notification letter, this figure is taken as the maximum number of records impacted.
## 2986                                                 Both the source and the letter confirm that two Maryland residents were affected by the breach incident. Since this is the only number provided, it is used as the 'Max Records Impacted'. No total number of records impacted was specified, hence 'UNKN' is used for that field.
## 1842                                                                           The breach notification letter and description of the breach both state that the personal information of 19 individuals was viewed, which is the number used for 'Max Records Impacted'. No other numbers are provided that could influence the maximum.
## 3371  The source document provided a specific figure of 874 individuals affected by the breach. Since this is the only numeric data available regarding the records impacted, and there is no information contradicting or supplementing this figure in the data provided, the maximum number of records impacted is identified as 874.
##       Information.Types Encryption.Status
## 18847   SENSITIVE-LOGIN       UNENCRYPTED
## 18895              UNKN              UNKN
## 25102              UNKN              UNKN
## 2986         IDENTIFIER       UNENCRYPTED
## 1842         IDENTIFIER       UNENCRYPTED
## 3371         IDENTIFIER       UNENCRYPTED
##                                                                                                                                                                                                                                                 Information.Types.Description
## 18847 The notification letter states that credit card details were exposed, including numbers, which indicates the breach of 'account log\\u2010in, financial account, debit card, or credit card number'. No mention of encryption implies the information was unencrypted.}
## 18895                                                                                                                                                                                The data breach notification provides no details on the types of information impacted.}]
## 25102                                                                    There is no information provided about the types of information impacted in the breach, so it is unknown whether personal identifiers, financial information or other types of data were involved.}]
## 2986                                                                                                The file contained individuals' names and addresses, which are unique personal identifiers and were likely unencrypted as they were accessible from an email attachment.}
## 1842                                                                                                  The exposed information includes names and account information which are identifiers and the breach notification does not mention that the information was encrypted.}]
## 3371                                                                                                                       The breach involved names and addresses which fall under the IDENTIFIER category, and there is no indication that this information was encrypted.}
##               State
## 18847      Maryland
## 18895       Indiana
## 25102       Indiana
## 2986       Maryland
## 1842  Massachusetts
## 3371           Unkn
##                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                         breach.location.explanation
## 18847                                                                                                                                                                                                                                                                                    The breach notification letter does not provide specific location details such as street, city, state, or ZIP code. The country 'United States' is assumed based on the notification to U.S. state authorities and involvement of the FBI.
## 18895                                                                                                                                                                                                                The breach notification does not provide specific location details of Julep Beauty Inc.'s data breach event. Since Julep Beauty Inc. is known to be based in the United States, the country is listed as 'United States', but other location details are classified as 'UNKN' due to insufficient information.
## 25102                                                                                                                                                                                                                                                                                                                               The breach notification does not provide any location details of GoHenry Ltd, and without additional information, it's not possible to accurately identify the location of the breached entity.
## 2986                                                                                                                                               The location of the breach is determined as Maryland, which is stated in the provided breach notification letter. It specifies that two Maryland residents were affected by the incident, and the incident was reported to the Maryland Attorney General. However, there is no specific street, city, or zip code provided for Tyler Technologies' address in the letter itself.
## 1842                                                                                                                                                                                                                                                    The location of the breached entity, LPL Financial LLC, can be identified from the data breach notification letter headed by the Senior Vice President, Associate Counsel of LPL Financial, which lists the address 'One Beacon Street, 22nd Floor, Boston, MA 02108-3106'.
## 3371  The breach notification does not provide a street address, city, state, or zip code for Rural Health Access Corporation. Since the data breach notification letter is not provided in the JSON data and no additional specific location details are present, the exact location cannot be determined. Therefore, all location fields are marked as 'UNKN' except for the country, which is assumed to be the United States as the incident involved the WV Department of Health and Human Resources Bureau for Public Health.
##                                                                                                                                                                                                                                                                                                                                                                                                                                                                              Description.of.Breach
## 18847 On May 8, 2015, The Institute for Financial Markets experienced a data breach due to malicious code from an IP address in the Russian Federation, exposing unencrypted credit card details and identifying information of 248 individuals. The breach type was classified as a hack. Immediate actions were taken to mitigate the issue, including notifying the FBI and credit card issuers. The breach was reported to authorities and affected individuals were notified on May 12, 2015.
## 18895                                                                                                                                                                                                                                                           Limited information is available regarding the breach at Julep Beauty Inc. It occurred on November 6, 2016, and was reported on December 9, 2016, affecting 606 records, with 13 of those being records from an unspecified state.
## 25102                                                                                                                                                                                                                    A data breach occurred at GoHenry Ltd on June 19, 2022. The specifics of the breach have not been disclosed, including what information was compromised. A total of 5,356 records were impacted according to the source, with 120 of those pertaining to state residents.
## 2986                                      On March 3, 2017, a Tyler Technologies Inc. employee inadvertently sent an email containing unencrypted personal information, including names, addresses, Social Security numbers, and possibly income information, to unauthorized recipients. Two Maryland residents were affected, and Tyler Technologies provided notification and offered credit monitoring services. The incident was reported to the Maryland Attorney General on March 13, 2017.
## 1842                                                             An incident at LPL Financial LLC on October 24, 2008, due to a third-party system error allowed unauthorized access, enabling four clients to view the personal information of 19 individuals, 12 of whom are from New Hampshire. This personal information included names and account details. The breach was reported to the New Hampshire State Attorney General and affected clients were offered credit monitoring services.
## 3371                                           Rural Health Access Corporation reported an unintended disclosure of PHI that affected 874 individuals. The PHI, including names, addresses, and treatment information, was shared un-deidentified with the WV Department of Health and Human Resources Bureau for Public Health without a business associate agreement. The data was subsequently destroyed by the recipient. The breach was reported to HHS, affected individuals, and the media.
##       Reported.Date.New Date.of.Breach.New Year.of.Breach normalized_records
## 18847        2015-05-12         2015-05-08           2015          1.240e-07
## 18895        2016-12-09         2016-11-09           2016          3.030e-07
## 25102        2023-06-08         2023-05-09           2023          2.678e-06
## 2986         2017-03-13         2017-02-11           2017          1.000e-09
## 1842         2009-02-02         2008-10-24           2008          9.500e-09
## 3371         2019-09-06         2019-07-08           2019          4.370e-07
##       attack_score industry_score severity_score severity_category
## 18847         0.10      0.4857143      0.1271429          Below500
## 18895         0.10      0.3571429      0.1014287               Low
## 25102         0.25      0.4857143      0.1721442              High
## 2986          0.70      0.1000000      0.2300000          Below500
## 1842          0.10      0.4857143      0.1271429          Below500
## 3371          0.70      0.6142857      0.3328574               Low
##       severity_score_categorical
## 18847                        Low
## 18895                        Low
## 25102                        Low
## 2986                         Low
## 1842                         Low
## 3371                         Med
head(val_data_sev)
##       Index                                                     Name.of.Entity
## 11571 16158                               Mechanical Construction Services Inc
## 7762  10871                                                 Fora Financial LLC
## 7604  10648                                                       CBLPath Inc.
## 5372   7563                                                   Dr. Gary W. Peer
## 24762 34486                                                        Ullico Inc.
## 17751 24744 Central States Southeast and Southwest Areas Health & Welfare Fund
##       Source Organization.Type
## 11571     NH               BSO
## 7762      MD               BSF
## 7604     HHS               MED
## 5372      MD               MED
## 24762     MD               BSF
## 17751    HHS               MED
##                                                                                                                                                                                                                                                                       Organization.Type.Explanation
## 11571                                                                                                                                                                           The entity's name suggests it is a construction company, which would classify it as 'Other Businesses' under 'BSO'.
## 7762                                                                                                                                                                  Fora Financial LLC appears to be a financial services company, fitting within the Financial Services Business classification.
## 7604  The entity's name, CBLPath, Inc., and its association with a business associate that assisted with the collection of unpaid accounts for patients, along with the type of information breached (patient data), suggests that this is a healthcare-related entity, likely covered under HIPAA.
## 5372                                                                                                                     The entity is a medical provider, as indicated by the designation 'M.D.' (Doctor of Medicine) and because the breach involved patient file systems and health information.
## 24762                                                                                          Ullico Inc. sounds like a shorthand for 'Union Labor Life Insurance Company', which falls under the category of financial services and insurance, thereby classifying it as a BSF organization type.
## 17751                                                                                                          The entity's name includes 'Health & Welfare Fund', indicating it is related to healthcare. The data breach notification source is HHS, which suggests it is a HIPAA-covered entity.
##       Breach.Type Breach.Input
## 11571        HACK         HACK
## 7762         INSD         INSD
## 7604         HACK         HACK
## 5372         HACK         HACK
## 24762        INSD         INSD
## 17751        DISC         DISC
##                                                                                                                                                                                                                                                                                                                                                            Breach.Type.Explanation
## 11571                                                                                                                                                The breach type is classified as HACK because the description specifies that an unauthorized party accessed an employee's email account through unauthorized means, which is indicative of a cyberattack or hacking incident.
## 7762                                                                             The breach occurred due to an 'unauthorized actions of an employee at an independent third-party lender', which is an insider incident. It is not related to physical document theft, hacking, or loss of portable devices, but an insider misusing access to share working capital applications.
## 7604                                                                                                                                                          The breach was due to a cyber-attack on its business associate, which typically involves unauthorized network access, exploitation of software vulnerabilities, or similar actions indicative of a hacking incident.
## 5372                                                                                                   The incident at Dr. Gary W. Peer's practice is classified as a HACK because the breach notification indicates that a malware attack was the likely cause of compromise to the information systems which could have resulted in unauthorized access to personal information.
## 24762 This data breach was a result of internal error in Ullico Inc.'s implementation of 'single sign-on' system that led to employees being able to access accounts of their peers. There was no malicious intent or external actors involved; it was an internal issue caused by a mismatch between user credentials and directory service, categorizing it as an insider issue.
## 17751                           The incident involved mailing welcome and enrollment packets to incorrect addresses due to a mismatched employee list provided by an employer, leading to the exposure of sensitive personal information. This suggests an unintended disclosure of data not involving hacking, intentional breach, or physical loss, thus classifying it as DISC.
##       Max.Records.Impacted
## 11571                    6
## 7762                     2
## 7604                141956
## 5372                  3400
## 24762                    8
## 17751                  634
##                                                                                                                                                                                                                                                                                                                                                                     Max.Records.Impacted.Explanation
## 11571 The breach notification letter explicitly states that six New Hampshire residents were affected by the data breach. There is no other information provided regarding the total number of records impacted from other sources or in the letter, leading to the use of 'UNKN' for those fields. Thus, the maximum records impacted is determined to be 6 based on the information in the letter.
## 7762                                                                                                                                                                                                          The data breach notification letter and the source both confirm that two Maryland residents were affected by the breach, which is the highest figure available from the provided data.
## 7604                                                                                                                                                                                                                                              The total of 141,956 records impacted was provided from the source, and this is the highest figure available as no other numeric data is provided.
## 5372                                                                                                                                                                           The data breach notification letter specifies that approximately 3,400 clients are potentially affected by the breach, and this figure is used for 'max_records_impacted' as it is the only concrete number provided.
## 24762                                                                                                                                              All available sources, including the breach notification letter and the state records impacted provided from source, specify that 8 Maryland residents were affected, which is the maximum number of records known to be impacted by this breach.
## 17751                                                                                                                                              The source provided a count of 634 records impacted, which is the only figure available and has been normalized into a whole number. There is no additional specified state impact figure, hence the 'Max Records Impacted' is identified as 634.
##       Information.Types Encryption.Status
## 11571        IDENTIFIER       UNENCRYPTED
## 7762         IDENTIFIER       UNENCRYPTED
## 7604         IDENTIFIER       UNENCRYPTED
## 5372               UNKN              UNKN
## 24762        IDENTIFIER       UNENCRYPTED
## 17751        IDENTIFIER       UNENCRYPTED
##                                                                                                                                                                             Information.Types.Description
## 11571             The breach potentially exposed names, addresses, social security numbers, and mother's maiden names which are considered identifiers and there is no mention of them being encrypted.}]
## 7762                                                   Names, dates of birth, contact information, and Social Security Numbers of the business owners were exposed and there's no mention of encryption.}
## 7604                             The breach involved names, addresses, phone numbers, birthdates, which are considered identifiable information and there is no indication that this data was encrypted.}
## 5372                                                                                                     The notification letter does not provide specific details on the types of information exposed.}]
## 24762                                                                          The notification letter specifies the exposure of names, addresses, and dates of births which are considered identifiers.}
## 17751 Names, birthdates, social security numbers, plan election information, and dependent information were involved. The information was sent to incorrect addresses, which means it was not encrypted.}
##               State
## 11571 New Hampshire
## 7762       Maryland
## 7604           Unkn
## 5372       Maryland
## 24762      Maryland
## 17751          Unkn
##                                                                                                                                                                                                                                                                                                                                                                          breach.location.explanation
## 11571                                The breach notification letter lists an address in Windham, NH, but it is unclear if this is the location of the breach. Since the breach involves unauthorized email access, the exact physical location is less relevant. No street address is provided; the zip code is inferred from the city and state mentioned in relation to the affected NH residents.
## 7762                                                                                                                                                                                 Although the specific location of Fora Financial LLC is not provided in the notice letter, the breach affected two Maryland residents, and the Attorney General of Maryland is addressed in the correspondence.
## 7604                                                                                                                                                                                              The location information such as street address, city, state, and zip code of CBLPath Inc. is not provided in the available data. However, CBLPath Inc. is known to be based in the United States.
## 5372  The breach notification letter does not provide a specific address for Dr. Gary W. Peer's practice. However, it mentions one Maryland resident being affected, and since the notification is addressed to the Office of the Attorney General of Maryland, it suggests that the practice is likely in Maryland, United States. No further details are provided to specify the city or zip code.
## 24762                                                                                                                         The breach notification letter was addressed to the Attorney General in Maryland, indicating that the breach impacted Maryland residents. However, no specific street, city, or zip code of Ullico Inc.'s location was provided within the breach notification letter.
## 17751                                        The breach notification does not provide any specific location details such as street address, city, state, or zip code for Central States Southeast and Southwest Areas Health & Welfare Fund. Therefore, all location fields are marked as 'UNKN' except for the country, which is identified as the United States by the nature of the organization.
##                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                Description.of.Breach
## 11571                                                                                                                       An unauthorized party accessed an employee's email account of Mechanical Construction Services Inc, potentially exposing personal identifiable information which included names, addresses, social security numbers, and mother's maiden names of its employees and subcontractors' employees. The incident was discovered on August 15, 2018, and affected six New Hampshire residents.
## 7762  Fora Financial LLC reported a data breach that occurred around June 20, 2018, where an unauthorized party obtained working capital applications due to an insider breach from a third-party lender\u0089Ûªs employee. Personal information of business owners, including identifiers and sensitive government information, as well as commercial information, was exposed. The breach affected two Maryland residents, and Fora Financial LLC is providing credit monitoring and has notified law enforcement.
## 7604                                                                                                                                                                                                                            CBLPath Inc. experienced a data breach due to a cyber-attack on its business associate, affecting the ePHI of 141,956 individuals. The exposed data included identifiers, health, and commercial information. The incident was reported to HHS, affected individuals, and the media.
## 5372              A data security incident at Dr. Gary W. Peer's practice was discovered on March 14, 2019, possibly due to a malware attack. The breach could have affected personal information in the Practice's files. Although patient files are believed to be untouched, the exact extent of access is uncertain. Approximately 3,400 clients are potentially affected, with one confirmed Maryland resident. The practice has notified all current and past patients and offered credit monitoring services.
## 24762             On May 8, 2018, Ullico Inc. experienced a data breach when 'single sign-on' functionality resulted in a login mismatch that allowed employees to access other employees' Dayforce accounts, potentially exposing names, addresses, dates of birth, and social security numbers of 8 Maryland residents. The issue was addressed on the same day, and affected individuals were offered 24 months of free credit monitoring services. The incident was reported to the Maryland AG on May 29, 2018.
## 17751                                             Central States Southeast and Southwest Areas Health & Welfare Fund mailed welcome and enrollment packets containing sensitive personal information to incorrect addresses due to an employer-provided mismatched employee list. This resulted in approximately 634 individuals' informations being exposed. The organization reported the breach to HHS, individuals, and the media and revised its procedures for sending mailings to prevent future occurrences.
##       Reported.Date.New Date.of.Breach.New Year.of.Breach normalized_records
## 11571        2018-09-04         2018-08-05           2018         3.0000e-09
## 7762         2018-07-31         2018-07-01           2018         1.0000e-09
## 7604         2019-07-15         2019-05-16           2019         7.0978e-05
## 5372         2019-04-18         2019-02-17           2019         1.7000e-06
## 24762        2018-05-29         2018-05-08           2018         4.0000e-09
## 17751        2018-01-23         2017-11-24           2017         3.1700e-07
##       attack_score industry_score severity_score severity_category
## 11571         0.10      0.1000000      0.0500000          Below500
## 7762          0.25      0.4857143      0.1721429          Below500
## 7604          0.10      0.6142857      0.1528926              High
## 5372          0.10      0.6142857      0.1528580               Med
## 24762         0.25      0.4857143      0.1721429          Below500
## 17751         0.70      0.6142857      0.3328573               Low
##       severity_score_categorical
## 11571                        Low
## 7762                         Low
## 7604                         Low
## 5372                         Low
## 24762                        Low
## 17751                        Med
head(test_data_sev)
##       Index                    Name.of.Entity Source Organization.Type
## 21860 30394     Health Recovery Services Inc.     IN               MED
## 25178 35057 Sheet Metal Local 36 Welfare Fund    HHS               MED
## 7630  10687            FastHealth Corporation     NH               MED
## 3621   5086                  1st Mariner Bank     MT               BSF
## 6285   8829                        Cvent Inc.     MT               BSO
## 1894   2678            360 Mortgage Group LLC     MD               BSF
##                                                                                                                                                                                                                                                                                        Organization.Type.Explanation
## 21860                                                       The data breach involved a cyber-attack that affected electronic protected health information (ePHI), and Health Recovery Services Inc. notified HHS, which is an indication that it is a healthcare provider and therefore should be classified as MED.
## 25178 The Sheet Metal Local 36 Welfare Fund is involved with Member Assistance Program (MAP) eligibility data, suggesting healthcare-related activities, and the breach involved protected health information (PHI). Also, the breach notification to HHS indicates that it is a healthcare entity covered by HIPAA.
## 7630                                                                                                                                              FastHealth Corporation provides operational and website services on behalf of healthcare providers, indicating that it is associated with the healthcare industry.
## 3621                                                                                                                                                                                             The name '1st Mariner Bank' suggests that it is a bank, which falls under the financial services industry category.
## 6285                                                                                                               Cvent Inc is a vendor involved in event registration processes, which suggests business support or services but doesn't clearly align with financial, retail, educational, or healthcare sectors.
## 1894                                                                                                                       360 Mortgage Group LLC appears to be involved in the mortgage industry, which is closely related to financial services. Therefore, it is classified as BSF (Financial Services Business).
##       Breach.Type Breach.Input
## 21860        UNKN         HACK
## 25178        DISC         DISC
## 7630         HACK         HACK
## 3621         HACK         HACK
## 6285         HACK         HACK
## 1894         HACK         HACK
##                                                                                                                                                                                                                                                                                Breach.Type.Explanation
## 21860                                                              There is limited information about the nature of the breach, with no specific details on how the breach occurred or the types of information compromised. Due to the lack of information, the breach type is classified as unknown.
## 25178                                                                       The breach involved an employee inadvertently uploading Excel spreadsheets with Personally Identifiable Information (PHI) to an unsecure website. This constitutes an unintentional disclosure due to mishandling of data.
## 7630  The description indicates that suspicious code was found on FastHealth Corporation's server, suggesting an unauthorized third party altered the code to capture payment card information during online transactions. This scenario is characteristic of a cyberattack, hence classified as HACK.
## 3621                                                                                                                                                            The breach was the result of phishing attacks leading to unauthorized access to email accounts, which is considered a form of hacking.
## 6285                                                               The breach at Cvent Inc. involved unauthorized access to sensitive login information, which is indicative of a cybersecurity incident such as system hacking or network intrusion rather than physical theft or insider misconduct.
## 1894                                                                                                                                                      The breach involved unauthorized access to an employee's email account, suggesting a cyberattack rather than physical theft or loss of data.
##       Max.Records.Impacted
## 21860                20485
## 25178                 4560
## 7630                     3
## 3621                     1
## 6285                     1
## 1894                     2
##                                                                                                                                                                                                                                                                                  Max.Records.Impacted.Explanation
## 21860 The source provided a count of 20,485 total records impacted, which is the highest and only specific figure available. Since there is no breach notification letter provided or any data regarding state records from it, the maximum number of records impacted is taken from the source figure of 20,485.
## 25178                                                                                   The source provided a specific number of total records impacted, which was 4,560. Since this is the only numeric data available and there is no indication of a larger number, it is used for the 'Max Records Impacted'.
## 7630                                                 The breach notification letter mentions that FastHealth is notifying 3 New Hampshire residents. Since the letter specifies the number of individuals being notified and other data fields are unavailable or unspecified, the maximum records impacted is 3.
## 3621                                The source provided a count of 1 state record impacted. The breach notification letter did not specify the total number of records impacted, leading to 'UNKN' for that field and all the other fields. Therefore, the maximum number of records impacted is identified as 1.
## 6285                                                                   The only specific figure provided is for state records impacted from the source, which is 1. There is no other numeric data provided to determine the total number of records impacted; therefore, the max records impacted is taken as 1.
## 1894                                                                                                                                       The source and the letter both provided a count of 2 state records impacted, which is the only specific figure available regarding the number of impacted individuals.
##       Information.Types Encryption.Status
## 21860              UNKN              UNKN
## 25178        IDENTIFIER       UNENCRYPTED
## 7630         IDENTIFIER       UNENCRYPTED
## 3621               UNKN       UNENCRYPTED
## 6285    SENSITIVE-LOGIN       UNENCRYPTED
## 1894         IDENTIFIER       UNENCRYPTED
##                                                                                                                                                                           Information.Types.Description
## 21860                                                                                                  There is no information provided on the types of information that were impacted by the breach.}]
## 25178           Names, addresses, and dates of birth of the affected individuals were included in the spreadsheets, and there is no indication in the description that this information was encrypted.}
## 7630                                                                   The breach exposed patients' names, billing addresses, email addresses, and phone numbers which were not reported as encrypted.}
## 3621  The letter mentions exposed elements but does not specify what type of information they are, and there is also no mention of encryption; therefore, they are marked as unknown and unencrypted.}]
## 6285       The letter suggests that there might be misuse of information, thus there could be unauthorized access to sensitive login information. The encryption status of the data is not mentioned.}]
## 1894                                                               Names, addresses, and phone numbers are mentioned as part of the breached data and there is no indication that they were encrypted.}
##               State
## 21860       Indiana
## 25178          Unkn
## 7630  New Hampshire
## 3621       Maryland
## 6285        Montana
## 1894          Texas
##                                                                                                                                                                                                                                                                                     breach.location.explanation
## 21860                                                                     The breach notification does not provide any specific location details of Health Recovery Services Inc., nor does the description. Without further information, it is not possible to determine the street, city, state, or zip code.
## 25178 The provided data does not contain specific location details such as street address, city, state, or zip code. Therefore, the country is set to 'United States' based on the context, since it involves PHI under the jurisdiction of HHS (Health and Human Services), a United States government entity.
## 7630                                                                                      The data breach notification does not provide specific location details for FastHealth Corporation. The street addresses included in the letter are for the law firm and attorney general, not the entity's location.
## 3621                                                                                          The breach notification letter from 1st Mariner Bank mentioned the Maryland Attorney General, suggesting that the entity is located in Maryland. No specific street, city or zip code was provided in the letter.
## 6285                                   The data breach notification does not provide specific location details of the breached entity. The use of Experian's services and reference to managing credit reports through major US credit bureaus suggest the country of the breached entity is the United States.
## 1894                                                                                        The Data Breach Notification Letter lists the address '11305 Four Points Drive Building 1 Suite 200 Austin, TX 78726' as the contact for 360 Mortgage Group LLC, indicating this is the breached entity's location.
##                                                                                                                                                                                                                                                                                                                                                                                                                                                                                         Description.of.Breach
## 21860                                                                                                                                                                                                                    There is limited information about the breach at Health Recovery Services Inc. It occurred on November 14, 2018, and was reported on April 5, 2019. The specific details of the breach, including the nature of the incident and types of information compromised, are not provided.
## 25178                                                                                                                                  An employee inadvertently uploaded Excel spreadsheets with PHI to an unsecure website, which were then spread to other websites. The data belonged to Sheet Metal Local 36 Welfare Fund members and included names, addresses, dates of birth, and social security numbers for 4,560 individuals. The breach was reported to HHS, affected individuals, and the media.
## 7630  A security incident occurred at FastHealth Corporation, discovered on December 21, 2016, where suspicious code aimed at capturing payment card information was found on a server. The breach affected platforms processing online payments between January 14, 2016, and December 20, 2016. The breach was reported to the Attorney General and affected individuals began being notified on May 26, 2017, and included personal identifiers and sensitive login details like payment card information.
## 3621                                                                                 1st Mariner Bank was affected by phishing attacks between September 21 and October 29, 2017, leading to unauthorized access to email accounts and potential exposure of personal information. They have provided credit monitoring and identity restoration services through Experian and have taken steps to enhance security measures such as changing login credentials and implementing multi-factor authentication.
## 6285                                                                                                        Cvent Inc experienced a data breach that may have led to unauthorized access to sensitive login information. Although the breach was detected on April 16, 2019, and reported by June 28, 2019, it's not clear to what extent the data was compromised or used maliciously. People affected were encouraged to use identity protection services provided by Experian and stay vigilant for fraud.
## 1894                                                                                                                       A breach at 360 Mortgage Group LLC involved unauthorized access to an employee email account between February 8, 2018, and April 11, 2018. The breach was reported on June 19, 2018. Names, addresses, phone numbers, social security numbers, and account numbers may have been accessed. Two Maryland residents were affected, and credit monitoring services have been offered.
##       Reported.Date.New Date.of.Breach.New Year.of.Breach normalized_records
## 21860        2019-04-05         2019-02-04           2019        1.02425e-05
## 25178        2013-07-15         2013-05-16           2013        2.28000e-06
## 7630         2017-05-26         2016-12-20           2016        1.50000e-09
## 3621         2018-02-15         2017-10-29           2017        5.00000e-10
## 6285         2019-06-28         2019-05-29           2019        5.00000e-10
## 1894         2018-06-19         2018-04-11           2018        1.00000e-09
##       attack_score industry_score severity_score severity_category
## 21860          0.1      0.6142857      0.1528623              High
## 25178          0.7      0.6142857      0.3328583              High
## 7630           0.1      0.6142857      0.1528571          Below500
## 3621           0.1      0.4857143      0.1271429          Below500
## 6285           0.1      0.1000000      0.0500000          Below500
## 1894           0.1      0.4857143      0.1271429          Below500
##       severity_score_categorical
## 21860                        Low
## 25178                        Med
## 7630                         Low
## 3621                         Low
## 6285                         Low
## 1894                         Low

RQ1

THE FOLLOWING MODELS ANSWER RESEARCH QUESTION 1-

RQ1: What are the characteristics of data breaches that predict the nature and severity of the attacks most effectively?

Separating the models

# Load necessary libraries
library(dplyr)
library(randomForest)
## randomForest 4.7-1.2
## Type rfNews() to see new features/changes/bug fixes.
library(xgboost)
library(caret)
## Loading required package: lattice
library(pROC)
## Type 'citation("pROC")' for a citation.
# Assume the datasets are already loaded into R as train_data_sev, val_data_sev, and test_data_sev

# Copy datasets and create new ones ending with _RQ1
train_data_RQ1 <- train_data_sev
val_data_RQ1 <- val_data_sev
test_data_RQ1 <- test_data_sev

# Create attack_size variable from the severity_category
train_data_RQ1 <- train_data_RQ1 %>% mutate(attack_size = as.factor(severity_category))
val_data_RQ1 <- val_data_RQ1 %>% mutate(attack_size = as.factor(severity_category))
test_data_RQ1 <- test_data_RQ1 %>% mutate(attack_size = as.factor(severity_category))

# Filter datasets to Organization.Type = "MED"
train_data_RQ1 <- train_data_RQ1 %>% dplyr::filter(Organization.Type == "MED")
val_data_RQ1 <- val_data_RQ1 %>% dplyr::filter(Organization.Type == "MED")
test_data_RQ1 <- test_data_RQ1 %>% dplyr::filter(Organization.Type == "MED")

# Convert character variables to numerical values
char_vars <- c("Breach.Input", "Information.Types", "Encryption.Status", "State")

for (var in char_vars) {
  levels <- unique(c(train_data_RQ1[[var]], val_data_RQ1[[var]], test_data_RQ1[[var]]))
  train_data_RQ1[[var]] <- as.numeric(factor(train_data_RQ1[[var]], levels = levels))
  val_data_RQ1[[var]] <- as.numeric(factor(val_data_RQ1[[var]], levels = levels))
  test_data_RQ1[[var]] <- as.numeric(factor(test_data_RQ1[[var]], levels = levels))
}

# Prepare data for modeling
predictors <- c("Breach.Input", "Information.Types", "Encryption.Status", "State")
response <- "attack_size"

train_matrix <- data.matrix(train_data_RQ1[, predictors])
train_labels <- as.numeric(train_data_RQ1[[response]])

val_matrix <- data.matrix(val_data_RQ1[, predictors])
val_labels <- as.numeric(val_data_RQ1[[response]])

test_matrix <- data.matrix(test_data_RQ1[, predictors])
test_labels <- as.numeric(test_data_RQ1[[response]])

# Ensure levels are consistent across datasets
# Convert back to factor for training Random Forest
train_labels_factor <- factor(train_labels, levels = unique(train_labels))
val_labels_factor <- factor(val_labels, levels = unique(val_labels))
test_labels_factor <- factor(test_labels, levels = unique(test_labels))

SMOTE to address class imbalance

library(themis)
## Loading required package: recipes
## 
## Attaching package: 'recipes'
## The following object is masked from 'package:stringr':
## 
##     fixed
## The following object is masked from 'package:stats':
## 
##     step
library(recipes)

# Check BEFORE
cat("Class distribution BEFORE SMOTE:\n")
## Class distribution BEFORE SMOTE:
print(table(train_labels_factor))
## train_labels_factor
##    3    4    1    2 
## 1303  509  382 1850
# Build data frame with features + label
train_df <- as.data.frame(train_matrix)
train_df$severity_category <- train_labels_factor

# Recipe-based SMOTE - handles all classes
smote_recipe <- recipe(severity_category ~ ., data = train_df) %>%
  step_smote(severity_category, over_ratio = 1, seed = 123)

smote_prep    <- prep(smote_recipe, training = train_df)
train_smoted  <- bake(smote_prep, new_data = NULL)

# Separate back into matrix and labels
train_matrix_smote <- data.matrix(train_smoted[, predictors])
train_labels_smote <- train_smoted$severity_category

# Check AFTER
cat("\nClass distribution AFTER SMOTE:\n")
## 
## Class distribution AFTER SMOTE:
print(table(train_labels_smote))
## train_labels_smote
##    3    4    1    2 
## 1850 1850 1850 1850
write.csv(train_smoted, "Data/train_data_RQ1_smote.csv", row.names = FALSE)

Find labels

levels(train_labels_factor)
## [1] "3" "4" "1" "2"

Label mapping

# Label mapping for severity categories
label_map <- c("1" = "Below500", "2" = "High", "3" = "Low", "4" = "Med")

# Verify mapping against actual distribution
cat("Label mapping verification:\n")
## Label mapping verification:
for (num in names(label_map)) {
  cat("Class", num, "=", label_map[num], "\n")
}
## Class 1 = Below500 
## Class 2 = High 
## Class 3 = Low 
## Class 4 = Med

RF

# Load necessary libraries
library(randomForest)
library(caret)
library(pROC)  # For ROC curves



# Train Random Forest model
rf_model_RQ1 <- randomForest(x = train_matrix, y = train_labels_factor, importance = TRUE)

# Predict using the trained Random Forest model and get class predictions
rf_predictions <- predict(rf_model_RQ1, test_matrix, type = "response")

# Convert predicted factors to numeric labels if necessary
rf_predictions_numeric <- as.numeric(rf_predictions)

# Function to evaluate the model
evaluate_rf_model <- function(true_labels, predictions) {
  true_labels_factor <- factor(true_labels, levels = unique(true_labels))
  predictions_factor <- factor(predictions, levels = levels(true_labels_factor))
  
  confusion <- confusionMatrix(predictions_factor, true_labels_factor)
  
  # For multiclass ROC, we need probabilities. Let's get them.
  # Since we already have class predictions, we need to adjust the function.
  # For ROC AUC in multiclass, we'll calculate the average AUC over all classes.
  
  # Calculate ROC AUC for each class
  roc_list <- multiclass.roc(true_labels, as.numeric(predictions_factor))
  auc_value <- auc(roc_list)
  
  list(
    accuracy = confusion$overall['Accuracy'],
    f1_score = confusion$byClass[,'F1'],
    precision = confusion$byClass[,'Precision'],
    recall = confusion$byClass[,'Recall'],
    roc_auc = auc_value,
    confusion_matrix = confusion$table
  )
}

# Evaluate the Random Forest model
rf_evaluation_RQ1 <- evaluate_rf_model(test_labels_factor, rf_predictions)
## Setting direction: controls < cases
## Setting direction: controls < cases
## Setting direction: controls < cases
## Setting direction: controls < cases
## Setting direction: controls < cases
## Setting direction: controls < cases
# Print Random Forest evaluation metrics
print("Random Forest RQ1 Evaluation:")
## [1] "Random Forest RQ1 Evaluation:"
print(rf_evaluation_RQ1)
## $accuracy
## Accuracy 
## 0.476652 
## 
## $f1_score
##   Class: 2   Class: 1   Class: 3   Class: 4 
## 0.58293076 0.01574803 0.47761194 0.02439024 
## 
## $precision
##  Class: 2  Class: 1  Class: 3  Class: 4 
## 0.4629156 0.3333333 0.5191740 0.1818182 
## 
## $recall
##    Class: 2    Class: 1    Class: 3    Class: 4 
## 0.786956522 0.008064516 0.442211055 0.013071895 
## 
## $roc_auc
## Multi-class area under the curve: 0.5509
## 
## $confusion_matrix
##           Reference
## Prediction   2   1   3   4
##          2 362 100 217 103
##          1   1   1   1   0
##          3  92  23 176  48
##          4   5   0   4   2

SMOTE RF TRAINING

### RF - SMOTE

# Align factor levels to match test set before training
train_labels_smote <- factor(train_labels_smote, 
                              levels = levels(test_labels_factor))

set.seed(123)

# Train
rf_model_RQ1_smote <- randomForest(x = train_matrix_smote, 
                                    y = train_labels_smote, 
                                    importance = TRUE)

# Predict and align prediction levels to test set
rf_predictions_smote <- predict(rf_model_RQ1_smote, test_matrix, type = "response")
rf_predictions_smote <- factor(rf_predictions_smote, 
                                levels = levels(test_labels_factor))

# Evaluate
rf_evaluation_RQ1_smote <- evaluate_rf_model(test_labels_factor, rf_predictions_smote)
## Setting direction: controls < cases
## Setting direction: controls < cases
## Setting direction: controls < cases
## Setting direction: controls < cases
## Setting direction: controls < cases
## Setting direction: controls > cases
print("Random Forest SMOTE RQ1 Evaluation:")
## [1] "Random Forest SMOTE RQ1 Evaluation:"
print(rf_evaluation_RQ1_smote)
## $accuracy
##  Accuracy 
## 0.4740088 
## 
## $f1_score
##  Class: 2  Class: 1  Class: 3  Class: 4 
## 0.5554360 0.5189189 0.4558011 0.1538462 
## 
## $precision
##  Class: 2  Class: 1  Class: 3  Class: 4 
## 0.5501066 0.3902439 0.5061350 0.2021277 
## 
## $recall
##  Class: 2  Class: 1  Class: 3  Class: 4 
## 0.5608696 0.7741935 0.4145729 0.1241830 
## 
## $roc_auc
## Multi-class area under the curve: 0.5846
## 
## $confusion_matrix
##           Reference
## Prediction   2   1   3   4
##          2 258  15 129  67
##          1  73  96  55  22
##          3 103  13 165  45
##          4  26   0  49  19

XGboost

### XGBoost - No SMOTE

library(xgboost)
library(caret)
library(pROC)

# Adjust labels for zero-based indexing
train_labels_xgb <- as.numeric(train_labels_factor) - 1
test_labels_xgb  <- as.numeric(test_labels_factor) - 1

# Create DMatrix objects
dtrain <- xgb.DMatrix(data = train_matrix, label = train_labels_xgb)
dtest  <- xgb.DMatrix(data = test_matrix,  label = test_labels_xgb)

# Set parameters
num_class <- length(levels(train_labels_factor))
params <- list(
  objective  = "multi:softprob",
  num_class  = num_class,
  eval_metric = "mlogloss"
)

set.seed(123)


# Train
xgb_model_RQ1 <- xgb.train(
  params  = params,
  data    = dtrain,
  nrounds = 100,
  verbose = 0
)

# Predict
xgb_predictions_prob        <- predict(xgb_model_RQ1, dtest)
xgb_predictions_prob_matrix <- matrix(xgb_predictions_prob, ncol = num_class, byrow = TRUE)
xgb_predictions_numeric     <- max.col(xgb_predictions_prob_matrix) - 1

# Evaluate
evaluate_xgb_model <- function(true_labels, predictions) {
  true_labels_factor  <- factor(true_labels,  levels = 0:(num_class - 1))
  predictions_factor  <- factor(predictions,  levels = 0:(num_class - 1))
  confusion  <- confusionMatrix(predictions_factor, true_labels_factor)
  roc_list   <- multiclass.roc(true_labels, predictions)
  auc_value  <- auc(roc_list)
  list(
    accuracy        = confusion$overall['Accuracy'],
    f1_score        = confusion$byClass[,'F1'],
    precision       = confusion$byClass[,'Precision'],
    recall          = confusion$byClass[,'Recall'],
    roc_auc         = auc_value,
    confusion_matrix = confusion$table
  )
}

xgb_evaluation_RQ1 <- evaluate_xgb_model(test_labels_xgb, xgb_predictions_numeric)
## Setting direction: controls > cases
## Setting direction: controls < cases
## Setting direction: controls < cases
## Setting direction: controls < cases
## Setting direction: controls < cases
## Setting direction: controls < cases
print("XGBoost RQ1 Evaluation:")
## [1] "XGBoost RQ1 Evaluation:"
print(xgb_evaluation_RQ1)
## $accuracy
##  Accuracy 
## 0.2352423 
## 
## $f1_score
##  Class: 0  Class: 1  Class: 2  Class: 3 
## 0.2625698 0.1360202 0.2948905 0.1906780 
## 
## $precision
##  Class: 0  Class: 1  Class: 2  Class: 3 
## 0.3671875 0.0989011 0.3519164 0.1410658 
## 
## $recall
##  Class: 0  Class: 1  Class: 2  Class: 3 
## 0.2043478 0.2177419 0.2537688 0.2941176 
## 
## $roc_auc
## Multi-class area under the curve: 0.5128
## 
## $confusion_matrix
##           Reference
## Prediction   0   1   2   3
##          0  94  37  91  34
##          1 110  27  95  41
##          2 127  26 101  33
##          3 129  34 111  45

SMOTE XGBOOST TRAINING

### XGBoost - SMOTE

# Align levels then convert to zero-based integers
train_labels_smote     <- factor(train_labels_smote, levels = levels(test_labels_factor))
train_labels_smote_xgb <- as.numeric(train_labels_smote) - 1

# Create DMatrix
dtrain_smote <- xgb.DMatrix(data = train_matrix_smote, label = train_labels_smote_xgb)

set.seed(123)


# Train
xgb_model_RQ1_smote <- xgb.train(
  params  = params,
  data    = dtrain_smote,
  nrounds = 100,
  verbose = 0
)

# Predict
xgb_predictions_prob_smote        <- predict(xgb_model_RQ1_smote, dtest)
xgb_predictions_prob_matrix_smote <- matrix(xgb_predictions_prob_smote, ncol = num_class, byrow = TRUE)
xgb_predictions_smote_numeric     <- max.col(xgb_predictions_prob_matrix_smote) - 1

# Evaluate
xgb_evaluation_RQ1_smote <- evaluate_xgb_model(test_labels_xgb, xgb_predictions_smote_numeric)
## Setting direction: controls < cases
## Setting direction: controls < cases
## Setting direction: controls < cases
## Setting direction: controls < cases
## Setting direction: controls > cases
## Setting direction: controls > cases
print("XGBoost SMOTE RQ1 Evaluation:")
## [1] "XGBoost SMOTE RQ1 Evaluation:"
print(xgb_evaluation_RQ1_smote)
## $accuracy
## Accuracy 
## 0.245815 
## 
## $f1_score
##  Class: 0  Class: 1  Class: 2  Class: 3 
## 0.3149606 0.1549637 0.2903704 0.1380952 
## 
## $precision
##  Class: 0  Class: 1  Class: 2  Class: 3 
## 0.3973510 0.1107266 0.3537906 0.1086142 
## 
## $recall
##  Class: 0  Class: 1  Class: 2  Class: 3 
## 0.2608696 0.2580645 0.2462312 0.1895425 
## 
## $roc_auc
## Multi-class area under the curve: 0.5309
## 
## $confusion_matrix
##           Reference
## Prediction   0   1   2   3
##          0 120  24 105  53
##          1 131  32  90  36
##          2 116  28  98  35
##          3  93  40 105  29
packageVersion("xgboost")
## [1] '3.2.1.1'

Class Distribution Before/After SMOTE

### Class Distribution Before/After SMOTE

library(ggplot2)
library(dplyr)

# Build data frame
dist_df <- data.frame(
  Class = rep(c("Below500", "High", "Low", "Med"), 2),
  Count = c(382, 1850, 1303, 509, 1850, 1850, 1850, 1850),
  Stage = rep(c("Before SMOTE", "After SMOTE"), each = 4)
)

dist_df$Class <- factor(dist_df$Class, levels = c("Below500", "Low", "Med", "High"))
dist_df$Stage <- factor(dist_df$Stage, levels = c("Before SMOTE", "After SMOTE"))

ggplot(dist_df, aes(x = Class, y = Count, fill = Stage)) +
  geom_bar(stat = "identity", position = position_dodge(width = 0.7), width = 0.6) +
  geom_text(aes(label = Count), position = position_dodge(width = 0.7),
            vjust = -0.4, size = 4) +
  scale_fill_brewer(palette = "Set2") +
  labs(title = "Class Distribution Before and After SMOTE",
       x = "Severity Category", y = "Count", fill = "") +
  ylim(0, 2200) +
  theme_minimal() +
  theme(plot.title = element_text(hjust = 0.5, size = 16, face = "bold"),
        axis.title = element_text(size = 13),
        axis.text = element_text(size = 11),
        legend.text = element_text(size = 11))

Feature importance plot RF

### RF Original Feature Importance

library(ggplot2)

importance_original <- as.data.frame(rf_model_RQ1$importance)
importance_original$Feature <- rownames(importance_original)

ggplot(importance_original, aes(x = reorder(Feature, MeanDecreaseGini),
                                 y = MeanDecreaseGini)) +
  geom_bar(stat = "identity", fill = "steelblue", width = 0.6) +
  coord_flip() +
  labs(title = "RF Original Feature Importance",
       x = "Feature", y = "Mean Decrease Gini") +
  theme_minimal() +
  theme(plot.title = element_text(hjust = 0.5, size = 16, face = "bold"),
        axis.title = element_text(size = 13),
        axis.text  = element_text(size = 11))

RF SMOTE feauture of importance

### RF SMOTE Feature Importance

library(ggplot2)

importance_smote <- as.data.frame(rf_model_RQ1_smote$importance)
importance_smote$Feature <- rownames(importance_smote)

# Map numeric codes back to names
importance_smote$Feature <- recode(importance_smote$Feature,
  "Breach.Input"     = "Breach Input",
  "Information.Types" = "Information Types",
  "Encryption.Status" = "Encryption Status",
  "State"            = "State"
)

ggplot(importance_smote, aes(x = reorder(Feature, MeanDecreaseGini),
                              y = MeanDecreaseGini)) +
  geom_bar(stat = "identity", fill = "steelblue", width = 0.6) +
  coord_flip() +
  labs(title = "RF SMOTE Feature Importance",
       x = "Feature", y = "Mean Decrease Gini") +
  theme_minimal() +
  theme(plot.title = element_text(hjust = 0.5, size = 16, face = "bold"),
        axis.title = element_text(size = 13),
        axis.text = element_text(size = 11))

Feautre importance XGBoost

# Load necessary library for plotting
library(ggplot2)

# Extract XGBoost feature importance
xgb_importance <- xgb.importance(model = xgb_model_RQ1)

# Order the importance by Gain in decreasing order
xgb_importance_df <- xgb_importance[order(xgb_importance$Gain, decreasing = TRUE), ]

# Plot XGBoost Feature Importance with a custom color (e.g., "blue")
ggplot(xgb_importance_df, aes(x = reorder(Feature, Gain), y = Gain)) +
  geom_bar(stat = "identity", fill = "blue") +  # Change fill color to blue
  coord_flip() +  # Flip coordinates for horizontal bars
  xlab("Feature") +
  ylab("Importance (Gain)") +
  ggtitle("XGBoost Feature Importance") +
  theme_minimal()  # Use a minimal theme for a cleaner look

XGBoost SMOTE Feature Importance

### XGBoost SMOTE Feature Importance

library(xgboost)
library(ggplot2)

xgb_importance_smote <- xgb.importance(model = xgb_model_RQ1_smote)

xgb_importance_smote$Feature <- recode(xgb_importance_smote$Feature,
  "Breach.Input"      = "Breach Input",
  "Information.Types" = "Information Types",
  "Encryption.Status" = "Encryption Status",
  "State"             = "State"
)

ggplot(xgb_importance_smote, aes(x = reorder(Feature, Gain), y = Gain)) +
  geom_bar(stat = "identity", fill = "darkorange", width = 0.6) +
  coord_flip() +
  labs(title = "XGBoost SMOTE Feature Importance",
       x = "Feature", y = "Gain") +
  theme_minimal() +
  theme(plot.title = element_text(hjust = 0.5, size = 16, face = "bold"),
        axis.title = element_text(size = 13),
        axis.text = element_text(size = 11))

Per-class recall comparison

### Per-Class Recall Comparison: Original vs SMOTE

library(ggplot2)
library(dplyr)

recall_df <- data.frame(
  Class = rep(c("Below500", "High", "Low", "Med"), 2),
  Recall = c(
    0.008064516, 0.786956522, 0.439698492, 0.013071895,  # RF Original
    0.7741935,   0.5608696,   0.4145729,   0.1241830     # RF SMOTE
  ),
  Model = rep(c("RF Original", "RF SMOTE"), each = 4)
)

recall_df$Class <- factor(recall_df$Class, 
                           levels = c("Below500", "Low", "Med", "High"))
recall_df$Model <- factor(recall_df$Model, 
                           levels = c("RF Original", "RF SMOTE"))

ggplot(recall_df, aes(x = Class, y = Recall, fill = Model)) +
  geom_bar(stat = "identity", position = position_dodge(width = 0.7), width = 0.6) +
  geom_text(aes(label = round(Recall, 3)), 
            position = position_dodge(width = 0.7),
            vjust = -0.4, size = 3.5) +
  scale_fill_brewer(palette = "Set1") +
  labs(title = "Per-Class Recall: RF Original vs RF SMOTE",
       x = "Severity Category", y = "Recall", fill = "") +
  ylim(0, 1.05) +
  theme_minimal() +
  theme(plot.title = element_text(hjust = 0.5, size = 16, face = "bold"),
        axis.title = element_text(size = 13),
        axis.text = element_text(size = 11),
        legend.text = element_text(size = 11))

ROC AUC PLOT

Comparison of both ROC AUC metrics

RF AUC Extraction

# Evaluate the Random Forest model
rf_evaluation_RQ1 <- evaluate_rf_model(test_labels_factor, rf_predictions)
## Setting direction: controls < cases
## Setting direction: controls < cases
## Setting direction: controls < cases
## Setting direction: controls < cases
## Setting direction: controls < cases
## Setting direction: controls < cases
# Extract the AUC value
rf_roc_auc <- rf_evaluation_RQ1$roc_auc

For the XGBoost AUC Extraction

# Evaluate the XGBoost model
xgb_evaluation_RQ1 <- evaluate_xgb_model(test_labels_xgb, xgb_predictions_numeric)
## Setting direction: controls > cases
## Setting direction: controls < cases
## Setting direction: controls < cases
## Setting direction: controls < cases
## Setting direction: controls < cases
## Setting direction: controls < cases
# Extract the AUC value
xgb_roc_auc <- xgb_evaluation_RQ1$roc_auc
# Create a vector of AUC values
auc_values <- c(rf_roc_auc, xgb_roc_auc)
model_names <- c("Random Forest", "XGBoost")

# Combine into a data frame
auc_data <- data.frame(Model = model_names, AUC = auc_values)

# Round AUC values for display
auc_data$AUC <- round(auc_data$AUC, 4)

# Load necessary library
library(ggplot2)

# Create a bar chart to compare AUC values
auc_plot <- ggplot(auc_data, aes(x = Model, y = AUC, fill = Model)) +
    geom_bar(stat = "identity", width = 0.5) +
    geom_text(aes(label = AUC), vjust = -0.3, size = 5) +
    ylim(0, 1) +
    labs(title = "Comparison of AUC Values", y = "AUC Value") +
    theme_minimal() +
    theme(legend.position = "none")

# Print the plot
print(auc_plot)

ROC curves

RF ROC AUC Plot

Corrected RF Original ROC Curves:

### ROC Curves - RF Original

library(pROC)
library(ggplot2)

rf_probs_original <- predict(rf_model_RQ1, test_matrix, type = "prob")

classes     <- levels(test_labels_factor)
class_names <- c("2" = "High", "1" = "Below500", "3" = "Low", "4" = "Med")
roc_df_rf   <- data.frame()

for (cls in classes) {
  binary_response <- ifelse(test_labels_factor == cls, 1, 0)
  if (length(unique(binary_response)) < 2) next
  
  col_index <- which(colnames(rf_probs_original) == cls)
  roc_obj   <- pROC::roc(binary_response, rf_probs_original[, col_index],
                          levels = c(0, 1), direction = "<")
  auc_val   <- round(pROC::auc(roc_obj), 3)
  
  temp_df <- data.frame(
    FPR   = 1 - roc_obj$specificities,
    TPR   = roc_obj$sensitivities,
    Class = paste0(class_names[cls], " (AUC=", auc_val, ")")
  )
  roc_df_rf <- rbind(roc_df_rf, temp_df)
}

ggplot(roc_df_rf, aes(x = FPR, y = TPR, color = Class)) +
  geom_line(size = 1) +
  geom_abline(intercept = 0, slope = 1, linetype = "dashed", color = "grey") +
  labs(title = "ROC Curves - RF Original",
       x = "False Positive Rate", y = "True Positive Rate", color = "Class") +
  theme_minimal() +
  theme(plot.title  = element_text(hjust = 0.5, size = 16, face = "bold"),
        axis.title  = element_text(size = 13),
        axis.text   = element_text(size = 11),
        legend.text = element_text(size = 10))
## Warning: Using `size` aesthetic for lines was deprecated in ggplot2 3.4.0.
## ℹ Please use `linewidth` instead.
## This warning is displayed once per session.
## Call `lifecycle::last_lifecycle_warnings()` to see where this warning was
## generated.

RF SMOTE ROC Curves

### RF SMOTE ROC Curves

library(pROC)
library(ggplot2)

# Get probability predictions from RF SMOTE
rf_probs_smote <- predict(rf_model_RQ1_smote, test_matrix, type = "prob")

classes     <- levels(test_labels_factor)
class_names <- c("2" = "High", "1" = "Below500", "3" = "Low", "4" = "Med")
roc_df      <- data.frame()

for (cls in classes) {
  binary_response <- ifelse(test_labels_factor == cls, 1, 0)
  if (length(unique(binary_response)) < 2) next
  
  col_index <- which(colnames(rf_probs_smote) == cls)
  roc_obj   <- pROC::roc(binary_response, rf_probs_smote[, col_index],
                          levels = c(0,1), direction = "<")
  auc_val   <- round(pROC::auc(roc_obj), 3)
  
  temp_df <- data.frame(
    FPR   = 1 - roc_obj$specificities,
    TPR   = roc_obj$sensitivities,
    Class = paste0(class_names[cls], " (AUC=", auc_val, ")")
  )
  roc_df <- rbind(roc_df, temp_df)
}

ggplot(roc_df, aes(x = FPR, y = TPR, color = Class)) +
  geom_line(size = 1) +
  geom_abline(intercept = 0, slope = 1, linetype = "dashed", color = "grey") +
  labs(title = "ROC Curves - RF SMOTE",
       x = "False Positive Rate", y = "True Positive Rate", color = "Class") +
  theme_minimal() +
  theme(plot.title = element_text(hjust = 0.5, size = 16, face = "bold"),
        axis.title = element_text(size = 13),
        axis.text  = element_text(size = 11),
        legend.text = element_text(size = 10))

XGBoost ROC AUC plot

Corrected XGBoost Original ROC Curves:

### ROC Curves - XGBoost Original

library(pROC)
library(ggplot2)

xgb_probs_original        <- predict(xgb_model_RQ1, dtest)
xgb_probs_matrix_original <- matrix(xgb_probs_original,
                                     ncol = num_class, byrow = TRUE)

class_names_xgb <- c("0" = "Low", "1" = "Med", "2" = "Below500", "3" = "High")
roc_df_xgb_orig <- data.frame()

for (i in 0:(num_class - 1)) {
  binary_response <- ifelse(test_labels_xgb == i, 1, 0)
  if (length(unique(binary_response)) < 2) next
  
  roc_obj <- pROC::roc(binary_response, xgb_probs_matrix_original[, i + 1],
                        levels = c(0, 1), direction = "<")
  auc_val <- round(pROC::auc(roc_obj), 3)
  
  temp_df <- data.frame(
    FPR   = 1 - roc_obj$specificities,
    TPR   = roc_obj$sensitivities,
    Class = paste0(class_names_xgb[as.character(i)], " (AUC=", auc_val, ")")
  )
  roc_df_xgb_orig <- rbind(roc_df_xgb_orig, temp_df)
}

ggplot(roc_df_xgb_orig, aes(x = FPR, y = TPR, color = Class)) +
  geom_line(size = 1) +
  geom_abline(intercept = 0, slope = 1, linetype = "dashed", color = "grey") +
  labs(title = "ROC Curves - XGBoost Original",
       x = "False Positive Rate", y = "True Positive Rate", color = "Class") +
  theme_minimal() +
  theme(plot.title  = element_text(hjust = 0.5, size = 16, face = "bold"),
        axis.title  = element_text(size = 13),
        axis.text   = element_text(size = 11),
        legend.text = element_text(size = 10))

XGBoost SMOTE ROC Curves

### XGBoost SMOTE ROC Curves

library(pROC)
library(ggplot2)

xgb_probs_smote        <- predict(xgb_model_RQ1_smote, dtest)
xgb_probs_matrix_smote <- matrix(xgb_probs_smote, 
                                   ncol = num_class, byrow = TRUE)

class_names_xgb <- c("0" = "Low", "1" = "Med", "2" = "Below500", "3" = "High")
roc_df_xgb      <- data.frame()

for (i in 0:(num_class - 1)) {
  binary_response <- ifelse(test_labels_xgb == i, 1, 0)
  if (length(unique(binary_response)) < 2) next
  
  roc_obj <- pROC::roc(binary_response, xgb_probs_matrix_smote[, i + 1],
                        levels = c(0,1), direction = "<")
  auc_val <- round(pROC::auc(roc_obj), 3)
  
  temp_df <- data.frame(
    FPR   = 1 - roc_obj$specificities,
    TPR   = roc_obj$sensitivities,
    Class = paste0(class_names_xgb[as.character(i)], " (AUC=", auc_val, ")")
  )
  roc_df_xgb <- rbind(roc_df_xgb, temp_df)
}

ggplot(roc_df_xgb, aes(x = FPR, y = TPR, color = Class)) +
  geom_line(size = 1) +
  geom_abline(intercept = 0, slope = 1, linetype = "dashed", color = "grey") +
  labs(title = "ROC Curves - XGBoost SMOTE",
       x = "False Positive Rate", y = "True Positive Rate", color = "Class") +
  theme_minimal() +
  theme(plot.title = element_text(hjust = 0.5, size = 16, face = "bold"),
        axis.title = element_text(size = 13),
        axis.text  = element_text(size = 11),
        legend.text = element_text(size = 10))

Spider Chart — All Four Models

### Spider Chart: All Four Models Comparison

# install.packages("fmsb")
library(fmsb)
## Registered S3 methods overwritten by 'fmsb':
##   method    from
##   print.roc pROC
##   plot.roc  pROC
# Build metrics — using macro-averaged values
spider_data <- data.frame(
  Accuracy  = c(1, 0, 0.4758, 0.4740, 0.2352, 0.2458),
  Precision = c(1, 0,
    mean(c(0.4623, 0.3333, 0.5178, 0.1818)),  # RF original
    mean(c(0.5501, 0.3902, 0.5061, 0.2021)),  # RF SMOTE
    mean(c(0.3672, 0.0989, 0.3519, 0.1411)),  # XGB original
    mean(c(0.3974, 0.1107, 0.3538, 0.1086))   # XGB SMOTE
  ),
  Recall    = c(1, 0,
    mean(c(0.7870, 0.0081, 0.4397, 0.0131)),
    mean(c(0.5609, 0.7742, 0.4146, 0.1242)),
    mean(c(0.2043, 0.2177, 0.2538, 0.2941)),
    mean(c(0.2609, 0.2581, 0.2462, 0.1895))
  ),
  F1_Score  = c(1, 0,
    mean(c(0.5825, 0.0157, 0.4755, 0.0244)),
    mean(c(0.5554, 0.5189, 0.4558, 0.1538)),
    mean(c(0.2626, 0.1360, 0.2949, 0.1907)),
    mean(c(0.3150, 0.1550, 0.2904, 0.1381))
  ),
  AUC       = c(1, 0, 0.5506, 0.5846, 0.5128, 0.5309)
)

rownames(spider_data) <- c("Max", "Min", 
                            "RF Original", "RF SMOTE",
                            "XGB Original", "XGB SMOTE")

colors_border <- c("steelblue", "darkgreen", "darkorange", "red")
colors_fill   <- c(rgb(0.27, 0.51, 0.71, 0.1),  # RF Original - steel blue
                   rgb(0,    0.55, 0,    0.1),   # RF SMOTE - dark green
                   rgb(1,    0.55, 0,    0.1),   # XGB Original - orange
                   rgb(1,    0,    0,    0.1))   # XGB SMOTE - red

radarchart(spider_data, axistype = 1,
           pcol = colors_border, pfcol = colors_fill,
           plwd = 2, plty = 1,
           cglcol = "grey", cglty = 1, axislabcol = "grey",
           caxislabels = seq(0, 1, 0.2), cglwd = 0.8,
           vlcex = 0.9,
           title = "Model Performance Comparison: Original vs SMOTE")

legend(x = "topright", inset = 0.05,
       legend = c("RF Original", "RF SMOTE", "XGB Original", "XGB SMOTE"),
       bty = "n", pch = 20, col = colors_border,
       text.col = "black", cex = 0.9, pt.cex = 1.5)

Extract Metrics

# RF Original
rf_accuracy   <- as.numeric(rf_evaluation_RQ1$accuracy)
rf_precision  <- mean(as.numeric(rf_evaluation_RQ1$precision), na.rm = TRUE)
rf_recall     <- mean(as.numeric(rf_evaluation_RQ1$recall), na.rm = TRUE)
rf_f1_score   <- mean(as.numeric(rf_evaluation_RQ1$f1_score), na.rm = TRUE)
rf_roc_auc    <- as.numeric(rf_evaluation_RQ1$roc_auc)

# RF SMOTE
rf_smote_accuracy  <- as.numeric(rf_evaluation_RQ1_smote$accuracy)
rf_smote_precision <- mean(as.numeric(rf_evaluation_RQ1_smote$precision), na.rm = TRUE)
rf_smote_recall    <- mean(as.numeric(rf_evaluation_RQ1_smote$recall), na.rm = TRUE)
rf_smote_f1_score  <- mean(as.numeric(rf_evaluation_RQ1_smote$f1_score), na.rm = TRUE)
rf_smote_roc_auc   <- as.numeric(rf_evaluation_RQ1_smote$roc_auc)

# XGBoost Original
xgb_accuracy  <- as.numeric(xgb_evaluation_RQ1$accuracy)
xgb_precision <- mean(as.numeric(xgb_evaluation_RQ1$precision), na.rm = TRUE)
xgb_recall    <- mean(as.numeric(xgb_evaluation_RQ1$recall), na.rm = TRUE)
xgb_f1_score  <- mean(as.numeric(xgb_evaluation_RQ1$f1_score), na.rm = TRUE)
xgb_roc_auc   <- as.numeric(xgb_evaluation_RQ1$roc_auc)

# XGBoost SMOTE
xgb_smote_accuracy  <- as.numeric(xgb_evaluation_RQ1_smote$accuracy)
xgb_smote_precision <- mean(as.numeric(xgb_evaluation_RQ1_smote$precision), na.rm = TRUE)
xgb_smote_recall    <- mean(as.numeric(xgb_evaluation_RQ1_smote$recall), na.rm = TRUE)
xgb_smote_f1_score  <- mean(as.numeric(xgb_evaluation_RQ1_smote$f1_score), na.rm = TRUE)
xgb_smote_roc_auc   <- as.numeric(xgb_evaluation_RQ1_smote$roc_auc)

# Print all four
cat("=== RF Original ===\n")
## === RF Original ===
cat("Accuracy:", rf_accuracy, "| Precision:", rf_precision, 
    "| Recall:", rf_recall, "| F1:", rf_f1_score, "| AUC:", rf_roc_auc, "\n\n")
## Accuracy: 0.476652 | Precision: 0.3743103 | Recall: 0.312576 | F1: 0.2751702 | AUC: 0.5508583
cat("=== RF SMOTE ===\n")
## === RF SMOTE ===
cat("Accuracy:", rf_smote_accuracy, "| Precision:", rf_smote_precision,
    "| Recall:", rf_smote_recall, "| F1:", rf_smote_f1_score, "| AUC:", rf_smote_roc_auc, "\n\n")
## Accuracy: 0.4740088 | Precision: 0.4121533 | Recall: 0.4684547 | F1: 0.4210005 | AUC: 0.5846316
cat("=== XGBoost Original ===\n")
## === XGBoost Original ===
cat("Accuracy:", xgb_accuracy, "| Precision:", xgb_precision,
    "| Recall:", xgb_recall, "| F1:", xgb_f1_score, "| AUC:", xgb_roc_auc, "\n\n")
## Accuracy: 0.2352423 | Precision: 0.2397677 | Recall: 0.2424941 | F1: 0.2210396 | AUC: 0.5127764
cat("=== XGBoost SMOTE ===\n")
## === XGBoost SMOTE ===
cat("Accuracy:", xgb_smote_accuracy, "| Precision:", xgb_smote_precision,
    "| Recall:", xgb_smote_recall, "| F1:", xgb_smote_f1_score, "| AUC:", xgb_smote_roc_auc, "\n")
## Accuracy: 0.245815 | Precision: 0.2426206 | Recall: 0.2386769 | F1: 0.2245975 | AUC: 0.5308823

Save final results to CSV

### Save Final Results to CSV

# Comparison table
results_table <- data.frame(
  Metric = c("Accuracy", "Precision", "Recall", "F1 Score", "AUC"),
  RF_Original  = c(rf_accuracy, rf_precision, rf_recall, 
                   rf_f1_score, rf_roc_auc),
  RF_SMOTE     = c(rf_smote_accuracy, rf_smote_precision, rf_smote_recall,
                   rf_smote_f1_score, rf_smote_roc_auc),
  XGB_Original = c(xgb_accuracy, xgb_precision, xgb_recall,
                   xgb_f1_score, xgb_roc_auc),
  XGB_SMOTE    = c(xgb_smote_accuracy, xgb_smote_precision, xgb_smote_recall,
                   xgb_smote_f1_score, xgb_smote_roc_auc)
)

# Round to 4 decimal places
results_table[, 2:5] <- round(results_table[, 2:5], 4)

# Save
write.csv(results_table, "Data/Outputs/SMOTE_Results_Final.csv", 
          row.names = FALSE)

# Per-class recall table
per_class_recall <- data.frame(
  Class        = c("Below500", "High", "Low", "Med"),
  RF_Original  = c(0.0081, 0.7870, 0.4397, 0.0131),
  RF_SMOTE     = c(0.7742, 0.5609, 0.4146, 0.1242),
  XGB_Original = c(0.2177, 0.2043, 0.2538, 0.2941),
  XGB_SMOTE    = c(0.2581, 0.2609, 0.2462, 0.1895)
)

write.csv(per_class_recall, "Data/Outputs/SMOTE_PerClass_Recall_Final.csv", 
          row.names = FALSE)

cat("Files saved:\n")
## Files saved:
cat("- Data/Outputs/SMOTE_Results_Final.csv\n")
## - Data/Outputs/SMOTE_Results_Final.csv
cat("- Data/Outputs/SMOTE_PerClass_Recall_Final.csv\n")
## - Data/Outputs/SMOTE_PerClass_Recall_Final.csv

Export all images

### Export All Plots to Outputs Folder

library(ggplot2)

# ── Helper: save with consistent dimensions ──────────────────────
save_plot <- function(plot_obj, filename, width = 8, height = 6) {
  ggsave(
    filename = paste0("Data/Outputs/", filename),
    plot     = plot_obj,
    width    = width,
    height   = height,
    dpi      = 300,
    bg       = "white"
  )
}

# ── Rebuild all plots as named objects ───────────────────────────

# 1. Class Distribution
p1 <- ggplot(dist_df, aes(x = Class, y = Count, fill = Stage)) +
  geom_bar(stat = "identity", position = position_dodge(width = 0.7), width = 0.6) +
  geom_text(aes(label = Count), position = position_dodge(width = 0.7),
            vjust = -0.4, size = 4) +
  scale_fill_brewer(palette = "Set2") +
  labs(title = "Class Distribution Before and After SMOTE",
       x = "Severity Category", y = "Count", fill = "") +
  ylim(0, 2200) +
  theme_minimal() +
  theme(plot.title = element_text(hjust = 0.5, size = 16, face = "bold"),
        axis.title = element_text(size = 13),
        axis.text  = element_text(size = 11),
        legend.text = element_text(size = 11))

# 2. RF Original Feature Importance
importance_original      <- as.data.frame(rf_model_RQ1$importance)
importance_original$Feature <- rownames(importance_original)

p2 <- ggplot(importance_original, 
             aes(x = reorder(Feature, MeanDecreaseGini), y = MeanDecreaseGini)) +
  geom_bar(stat = "identity", fill = "steelblue", width = 0.6) +
  coord_flip() +
  labs(title = "RF Original Feature Importance",
       x = "Feature", y = "Mean Decrease Gini") +
  theme_minimal() +
  theme(plot.title = element_text(hjust = 0.5, size = 16, face = "bold"),
        axis.title = element_text(size = 13),
        axis.text  = element_text(size = 11))

# 3. RF SMOTE Feature Importance
importance_smote         <- as.data.frame(rf_model_RQ1_smote$importance)
importance_smote$Feature <- rownames(importance_smote)
importance_smote$Feature <- dplyr::recode(importance_smote$Feature,
  "Breach.Input"      = "Breach Input",
  "Information.Types" = "Information Types",
  "Encryption.Status" = "Encryption Status",
  "State"             = "State")

p3 <- ggplot(importance_smote,
             aes(x = reorder(Feature, MeanDecreaseGini), y = MeanDecreaseGini)) +
  geom_bar(stat = "identity", fill = "steelblue4", width = 0.6) +
  coord_flip() +
  labs(title = "RF SMOTE Feature Importance",
       x = "Feature", y = "Mean Decrease Gini") +
  theme_minimal() +
  theme(plot.title = element_text(hjust = 0.5, size = 16, face = "bold"),
        axis.title = element_text(size = 13),
        axis.text  = element_text(size = 11))

# 4. XGBoost Original Feature Importance
xgb_importance_orig         <- xgb.importance(model = xgb_model_RQ1)
xgb_importance_orig$Feature <- dplyr::recode(xgb_importance_orig$Feature,
  "Breach.Input"      = "Breach Input",
  "Information.Types" = "Information Types",
  "Encryption.Status" = "Encryption Status",
  "State"             = "State")

p4 <- ggplot(xgb_importance_orig, 
             aes(x = reorder(Feature, Gain), y = Gain)) +
  geom_bar(stat = "identity", fill = "blue", width = 0.6) +
  coord_flip() +
  labs(title = "XGBoost Original Feature Importance",
       x = "Feature", y = "Gain") +
  theme_minimal() +
  theme(plot.title = element_text(hjust = 0.5, size = 16, face = "bold"),
        axis.title = element_text(size = 13),
        axis.text  = element_text(size = 11))

# 5. XGBoost SMOTE Feature Importance
xgb_importance_smote         <- xgb.importance(model = xgb_model_RQ1_smote)
xgb_importance_smote$Feature <- dplyr::recode(xgb_importance_smote$Feature,
  "Breach.Input"      = "Breach Input",
  "Information.Types" = "Information Types",
  "Encryption.Status" = "Encryption Status",
  "State"             = "State")

p5 <- ggplot(xgb_importance_smote,
             aes(x = reorder(Feature, Gain), y = Gain)) +
  geom_bar(stat = "identity", fill = "darkorange", width = 0.6) +
  coord_flip() +
  labs(title = "XGBoost SMOTE Feature Importance",
       x = "Feature", y = "Gain") +
  theme_minimal() +
  theme(plot.title = element_text(hjust = 0.5, size = 16, face = "bold"),
        axis.title = element_text(size = 13),
        axis.text  = element_text(size = 11))

# 6. Per-Class Recall Comparison
p6 <- ggplot(recall_df, aes(x = Class, y = Recall, fill = Model)) +
  geom_bar(stat = "identity", position = position_dodge(width = 0.7), width = 0.6) +
  geom_text(aes(label = round(Recall, 3)),
            position = position_dodge(width = 0.7),
            vjust = -0.4, size = 3.5) +
  scale_fill_brewer(palette = "Set1") +
  labs(title = "Per-Class Recall: RF Original vs RF SMOTE",
       x = "Severity Category", y = "Recall", fill = "") +
  ylim(0, 1.05) +
  theme_minimal() +
  theme(plot.title  = element_text(hjust = 0.5, size = 16, face = "bold"),
        axis.title  = element_text(size = 13),
        axis.text   = element_text(size = 11),
        legend.text = element_text(size = 11))

# 7. RF Original ROC
p7 <- ggplot(roc_df_rf, aes(x = FPR, y = TPR, color = Class)) +
  geom_line(size = 1) +
  geom_abline(intercept = 0, slope = 1, linetype = "dashed", color = "grey") +
  labs(title = "ROC Curves - RF Original",
       x = "False Positive Rate", y = "True Positive Rate", color = "Class") +
  theme_minimal() +
  theme(plot.title  = element_text(hjust = 0.5, size = 16, face = "bold"),
        axis.title  = element_text(size = 13),
        axis.text   = element_text(size = 11),
        legend.text = element_text(size = 10))

# 8. RF SMOTE ROC
p8 <- ggplot(roc_df, aes(x = FPR, y = TPR, color = Class)) +
  geom_line(size = 1) +
  geom_abline(intercept = 0, slope = 1, linetype = "dashed", color = "grey") +
  labs(title = "ROC Curves - RF SMOTE",
       x = "False Positive Rate", y = "True Positive Rate", color = "Class") +
  theme_minimal() +
  theme(plot.title  = element_text(hjust = 0.5, size = 16, face = "bold"),
        axis.title  = element_text(size = 13),
        axis.text   = element_text(size = 11),
        legend.text = element_text(size = 10))

# 9. XGBoost Original ROC
p9 <- ggplot(roc_df_xgb_orig, aes(x = FPR, y = TPR, color = Class)) +
  geom_line(size = 1) +
  geom_abline(intercept = 0, slope = 1, linetype = "dashed", color = "grey") +
  labs(title = "ROC Curves - XGBoost Original",
       x = "False Positive Rate", y = "True Positive Rate", color = "Class") +
  theme_minimal() +
  theme(plot.title  = element_text(hjust = 0.5, size = 16, face = "bold"),
        axis.title  = element_text(size = 13),
        axis.text   = element_text(size = 11),
        legend.text = element_text(size = 10))

# 10. XGBoost SMOTE ROC
p10 <- ggplot(roc_df_xgb, aes(x = FPR, y = TPR, color = Class)) +
  geom_line(size = 1) +
  geom_abline(intercept = 0, slope = 1, linetype = "dashed", color = "grey") +
  labs(title = "ROC Curves - XGBoost SMOTE",
       x = "False Positive Rate", y = "True Positive Rate", color = "Class") +
  theme_minimal() +
  theme(plot.title  = element_text(hjust = 0.5, size = 16, face = "bold"),
        axis.title  = element_text(size = 13),
        axis.text   = element_text(size = 11),
        legend.text = element_text(size = 10))

# ── Save all plots ────────────────────────────────────────────────
save_plot(p1,  "Fig01_Class_Distribution_SMOTE.png")
save_plot(p2,  "Fig02_RF_Original_Feature_Importance.png")
save_plot(p3,  "Fig03_RF_SMOTE_Feature_Importance.png")
save_plot(p4,  "Fig04_XGB_Original_Feature_Importance.png")
save_plot(p5,  "Fig05_XGB_SMOTE_Feature_Importance.png")
save_plot(p6,  "Fig06_PerClass_Recall_Comparison.png")
save_plot(p7,  "Fig07_RF_Original_ROC.png")
save_plot(p8,  "Fig08_RF_SMOTE_ROC.png")
save_plot(p9,  "Fig09_XGB_Original_ROC.png")
save_plot(p10, "Fig10_XGB_SMOTE_ROC.png")

cat("All 10 plots saved to Data/Outputs/\n")
## All 10 plots saved to Data/Outputs/

Spider graphs export

png("Data/Outputs/Fig11_Spider_Chart.png", 
    width = 800, height = 600, res = 120)

radarchart(spider_data, axistype = 1,
           pcol = colors_border, pfcol = colors_fill,
           plwd = 2, plty = 1,
           cglcol = "grey", cglty = 1, axislabcol = "grey",
           caxislabels = seq(0, 1, 0.2), cglwd = 0.8,
           vlcex = 0.9,
           title = "Model Performance Comparison: Original vs SMOTE")

legend(x = "topright", inset = 0.05,
       legend = c("RF Original", "RF SMOTE", "XGB Original", "XGB SMOTE"),
       bty = "n", pch = 20, col = colors_border,
       text.col = "black", cex = 0.9, pt.cex = 1.5)

dev.off()
## quartz_off_screen 
##                 2
cat("Spider chart saved to Data/Outputs/Fig11_Spider_Chart.png\n")
## Spider chart saved to Data/Outputs/Fig11_Spider_Chart.png

RQ2

THE FOLLOWING MODELS ANSWER RESEARCH QUESTION 2-

RQ2: Does the number of records affected is correlated to the severity of the cyber-attack?

Model 1

Load libraries

# Load necessary libraries
library(caret)
library(randomForest)
library(nnet)
library(e1071)
library(kernlab)
library(naivebayes)
## naivebayes 1.0.0 loaded
## For more information please visit:
## https://majkamichal.github.io/naivebayes/
library(fmsb)     # For radar chart
library(dplyr)    # For data manipulation
# ── RQ2 DATA PREP ─────
# Copy from the full (unfiltered) severity dataset — RQ2 uses all org types
train_data_RQ2 <- train_data_sev
val_data_RQ2   <- val_data_sev
test_data_RQ2  <- test_data_sev

# Build attack_severity_label from attack type and industry ONLY
# normalized_records is intentionally excluded so it can serve as a genuine
# independent predictor of this label (avoids the circular scoring problem)

build_attack_severity <- function(df) {
  composite <- 0.6 * df$attack_score + 0.4 * df$industry_score
  breaks <- quantile(composite, probs = c(0, 1/3, 2/3, 1), na.rm = TRUE)
  df$attack_severity_label <- cut(
    composite,
    breaks  = breaks,
    labels  = c("Low", "Med", "High"),
    include.lowest = TRUE
  )
  df$attack_severity_label <- factor(df$attack_severity_label,
                                     levels = c("Low", "Med", "High"))
  df
}

train_data_RQ2 <- build_attack_severity(train_data_RQ2)
val_data_RQ2   <- build_attack_severity(val_data_RQ2)
test_data_RQ2  <- build_attack_severity(test_data_RQ2)

# Confirm label distribution across splits
cat("Train label distribution:\n"); print(table(train_data_RQ2$attack_severity_label))
## Train label distribution:
## 
##  Low  Med High 
## 6629 5172 5878
cat("Val label distribution:\n");   print(table(val_data_RQ2$attack_severity_label))
## Val label distribution:
## 
##  Low  Med High 
##  980  743  802
cat("Test label distribution:\n");  print(table(test_data_RQ2$attack_severity_label))
## Test label distribution:
## 
##  Low  Med High 
## 1927 1481 1644

Ensure data consistency

# Convert character columns to factors
train_data_RQ2 <- train_data_RQ2 %>% mutate_if(is.character, as.factor)
val_data_RQ2 <- val_data_RQ2 %>% mutate_if(is.character, as.factor)
test_data_RQ2 <- test_data_RQ2 %>% mutate_if(is.character, as.factor)

# Define the formula
formula <- attack_severity_label ~ normalized_records

Define control for cross validation

# Correct the trainControl() configuration for cross-validation
control <- trainControl(method = "cv", number = 5)

Function to calculate metrics

calculate_metrics <- function(predictions, true_labels) {
  cm <- confusionMatrix(predictions, true_labels)
  accuracy <- cm$overall['Accuracy']
  precision <- mean(cm$byClass[,'Precision'], na.rm = TRUE)
  recall <- mean(cm$byClass[,'Recall'], na.rm = TRUE)
  f1_score <- mean(cm$byClass[,'F1'], na.rm = TRUE)
  
  return(list(Accuracy = as.numeric(accuracy),
              Precision = as.numeric(precision),
              Recall = as.numeric(recall),
              F1_Score = as.numeric(f1_score)))
}

RF

# Train Random Forest model
set.seed(123)
rfRQ2 <- train(formula, data = train_data_RQ2, method = 'rf', trControl = control, tuneGrid = expand.grid(mtry = 2))
## Warning in randomForest.default(x, y, mtry = param$mtry, ...): invalid mtry:
## reset to within valid range
## Warning in randomForest.default(x, y, mtry = param$mtry, ...): invalid mtry:
## reset to within valid range
## Warning in randomForest.default(x, y, mtry = param$mtry, ...): invalid mtry:
## reset to within valid range
## Warning in randomForest.default(x, y, mtry = param$mtry, ...): invalid mtry:
## reset to within valid range
## Warning in randomForest.default(x, y, mtry = param$mtry, ...): invalid mtry:
## reset to within valid range
## Warning in randomForest.default(x, y, mtry = param$mtry, ...): invalid mtry:
## reset to within valid range
# Predict on test data
pred_rf <- predict(rfRQ2, newdata = test_data_RQ2)

# Calculate metrics
metrics_rf <- calculate_metrics(pred_rf, test_data_RQ2$attack_severity_label)
cat("Random Forest Metrics:\n"); print(metrics_rf)
## Random Forest Metrics:
## $Accuracy
## [1] 0.4412114
## 
## $Precision
## [1] 0.430774
## 
## $Recall
## [1] 0.4165142
## 
## $F1_Score
## [1] 0.3985085

Multinomial

# Train Multinomial Logistic Regression model
set.seed(123)
multinomRQ2 <- train(formula, data = train_data_RQ2, method = 'multinom', trControl = control)
## # weights:  9 (4 variable)
## initial  value 15536.574986 
## iter  10 value 15455.236338
## iter  20 value 15454.757059
## iter  20 value 15454.757017
## iter  20 value 15454.757016
## final  value 15454.757016 
## converged
## # weights:  9 (4 variable)
## initial  value 15536.574986 
## final  value 15461.971218 
## converged
## # weights:  9 (4 variable)
## initial  value 15536.574986 
## iter  10 value 15455.309387
## final  value 15454.856933 
## converged
## # weights:  9 (4 variable)
## initial  value 15538.772211 
## iter  10 value 15460.206443
## iter  20 value 15459.781776
## iter  20 value 15459.781698
## iter  20 value 15459.781690
## final  value 15459.781690 
## converged
## # weights:  9 (4 variable)
## initial  value 15538.772211 
## final  value 15465.208892 
## converged
## # weights:  9 (4 variable)
## initial  value 15538.772211 
## iter  10 value 15460.269935
## iter  20 value 15459.869350
## iter  20 value 15459.869283
## iter  20 value 15459.869276
## final  value 15459.869276 
## converged
## # weights:  9 (4 variable)
## initial  value 15537.673599 
## iter  10 value 15456.312188
## iter  20 value 15455.843156
## iter  20 value 15455.843110
## iter  20 value 15455.843109
## final  value 15455.843109 
## converged
## # weights:  9 (4 variable)
## initial  value 15537.673599 
## final  value 15462.948150 
## converged
## # weights:  9 (4 variable)
## initial  value 15537.673599 
## iter  10 value 15456.373062
## iter  20 value 15455.926776
## iter  20 value 15455.926730
## iter  20 value 15455.926729
## final  value 15455.926729 
## converged
## # weights:  9 (4 variable)
## initial  value 15537.673599 
## iter  10 value 15458.987520
## iter  20 value 15458.940475
## final  value 15458.917865 
## converged
## # weights:  9 (4 variable)
## initial  value 15537.673599 
## final  value 15464.285149 
## converged
## # weights:  9 (4 variable)
## initial  value 15537.673599 
## iter  10 value 15459.067806
## iter  20 value 15459.028917
## final  value 15459.010923 
## converged
## # weights:  9 (4 variable)
## initial  value 15538.772211 
## iter  10 value 15458.442190
## iter  20 value 15457.747541
## iter  20 value 15457.747413
## iter  20 value 15457.747405
## final  value 15457.747405 
## converged
## # weights:  9 (4 variable)
## initial  value 15538.772211 
## final  value 15464.602389 
## converged
## # weights:  9 (4 variable)
## initial  value 15538.772211 
## iter  10 value 15458.524100
## iter  20 value 15457.871864
## iter  20 value 15457.871747
## iter  20 value 15457.871741
## final  value 15457.871741 
## converged
## # weights:  9 (4 variable)
## initial  value 19422.366651 
## final  value 19329.454384 
## converged
# Predict on test data
pred_multinom <- predict(multinomRQ2, newdata = test_data_RQ2)

# Calculate metrics
metrics_multinom <- calculate_metrics(pred_multinom, test_data_RQ2$attack_severity_label)
# Multinomial
cat("Multinomial Metrics:\n"); print(metrics_multinom)
## Multinomial Metrics:
## $Accuracy
## [1] 0.3814331
## 
## $Precision
## [1] 0.3814331
## 
## $Recall
## [1] 0.3333333
## 
## $F1_Score
## [1] 0.5522281

Naive Bayes

# Train Naive Bayes model
set.seed(123)
nbRQ2 <- train(formula, data = train_data_RQ2, method = 'naive_bayes', trControl = control)

# Predict on test data
pred_nb <- predict(nbRQ2, newdata = test_data_RQ2)

# Calculate metrics
metrics_nb <- calculate_metrics(pred_nb, test_data_RQ2$attack_severity_label)
# Naive Bayes
cat("Naive Bayes Metrics:\n"); print(metrics_nb)
## Naive Bayes Metrics:
## $Accuracy
## [1] 0.3820269
## 
## $Precision
## [1] 0.7105376
## 
## $Recall
## [1] 0.3340383
## 
## $F1_Score
## [1] 0.1858645

SVM

# Train SVM model
set.seed(123)
svmRQ2 <- train(formula, data = train_data_RQ2, method = 'svmRadial', trControl = control)

# Predict on test data
pred_svm <- predict(svmRQ2, newdata = test_data_RQ2)

# Calculate metrics
metrics_svm <- calculate_metrics(pred_svm, test_data_RQ2$attack_severity_label)
# SVM
cat("SVM Metrics:\n"); print(metrics_svm)
## SVM Metrics:
## $Accuracy
## [1] 0.4422011
## 
## $Precision
## [1] 0.4078497
## 
## $Recall
## [1] 0.4180312
## 
## $F1_Score
## [1] 0.358579

Ensemble Model Majority voting

# --- Ensemble Model (Best 3 by Accuracy) ---

# Collect all individual model metrics
all_model_metrics <- list(
  "Random Forest" = metrics_rf,
  "Multinomial"   = metrics_multinom,
  "Naive Bayes"   = metrics_nb,
  "SVM"           = metrics_svm
)

# Rank models by accuracy and select top 3
accuracy_scores <- sapply(all_model_metrics, function(m) m$Accuracy)
top3_names <- names(sort(accuracy_scores, decreasing = TRUE))[1:3]
cat("Top 3 models selected for ensemble:", paste(top3_names, collapse = ", "), "\n")
## Top 3 models selected for ensemble: SVM, Random Forest, Naive Bayes
# Build model list from top 3
all_models <- list(
  "Random Forest" = rfRQ2,
  "Multinomial"   = multinomRQ2,
  "Naive Bayes"   = nbRQ2,
  "SVM"           = svmRQ2
)
model_list <- all_models[top3_names]

# Collect predictions from top 3 models
predictions <- lapply(model_list, function(model) {
  predict(model, newdata = test_data_RQ2)
})

# Combine predictions into a data frame
pred_df <- as.data.frame(predictions)

# Apply majority voting
ensemble_prediction <- apply(pred_df, 1, function(row) {
  names(sort(table(row), decreasing = TRUE))[1]
})

# Convert to factor with correct levels
ensemble_prediction <- factor(ensemble_prediction, 
                              levels = levels(test_data_RQ2$attack_severity_label))

# Calculate metrics
metrics_ensemble <- calculate_metrics(ensemble_prediction, 
                                      test_data_RQ2$attack_severity_label)

cat("Ensemble Metrics:\n")
## Ensemble Metrics:
print(metrics_ensemble)
## $Accuracy
## [1] 0.4479414
## 
## $Precision
## [1] 0.4396482
## 
## $Recall
## [1] 0.4151196
## 
## $F1_Score
## [1] 0.3519461

Models metrics

# Function to get model predictions and metrics
get_model_metrics <- function(model, model_name) {
  predictions <- predict(model, newdata = test_data_RQ2)
  metrics <- calculate_metrics(predictions, test_data_RQ2$attack_severity_label)
  return(list(Model = model_name, Metrics = metrics))
}

# Get metrics for each model
metrics_list <- lapply(names(model_list), function(name) {
  get_model_metrics(model_list[[name]], name)
})

# Combine metrics into a data frame
metrics_df <- do.call(rbind, lapply(metrics_list, function(x) {
  data.frame(Model = x$Model, x$Metrics)
}))

# Add ensemble metrics
metrics_df <- rbind(metrics_df, data.frame(Model = "Ensemble", metrics_ensemble))

# Display the metrics data frame
print("Test Set Metrics:")
## [1] "Test Set Metrics:"
print(metrics_df)
##           Model  Accuracy Precision    Recall  F1_Score
## 1           SVM 0.4422011 0.4078497 0.4180312 0.3585790
## 2 Random Forest 0.4406176 0.4299702 0.4159060 0.3977929
## 3   Naive Bayes 0.3820269 0.7105376 0.3340383 0.1858645
## 4      Ensemble 0.4479414 0.4396482 0.4151196 0.3519461

Confusion Matrices

# --- Function to Print Confusion Matrix ---
print_confusion_matrix <- function(predictions, true_labels, model_name) {
  cm <- confusionMatrix(predictions, true_labels)
  
  cat("\nConfusion Matrix for", model_name, "model:\n")
  print(cm$table)
}

# --- Print Confusion Matrices ---
# Confusion Matrix for Random Forest
print_confusion_matrix(pred_rf, test_data_RQ2$attack_severity_label, "Random Forest")
## 
## Confusion Matrix for Random Forest model:
##           Reference
## Prediction  Low  Med High
##       Low  1408  910  833
##       Med   216  291  281
##       High  303  280  530
# Confusion Matrix for Multinomial Logistic Regression
print_confusion_matrix(pred_multinom, test_data_RQ2$attack_severity_label, "Multinomial Logistic Regression")
## 
## Confusion Matrix for Multinomial Logistic Regression model:
##           Reference
## Prediction  Low  Med High
##       Low  1927 1481 1644
##       Med     0    0    0
##       High    0    0    0
# Confusion Matrix for Naive Bayes
print_confusion_matrix(pred_nb, test_data_RQ2$attack_severity_label, "Naive Bayes")
## 
## Confusion Matrix for Naive Bayes model:
##           Reference
## Prediction  Low  Med High
##       Low  1926 1478 1643
##       Med     1    3    0
##       High    0    0    1
# Confusion Matrix for SVM
print_confusion_matrix(pred_svm, test_data_RQ2$attack_severity_label, "SVM")
## 
## Confusion Matrix for SVM model:
##           Reference
## Prediction  Low  Med High
##       Low  1200  779  603
##       Med    28   36   43
##       High  699  666  998
# Confusion Matrix for Ensemble
print_confusion_matrix(ensemble_prediction, test_data_RQ2$attack_severity_label, "Ensemble")
## 
## Confusion Matrix for Ensemble model:
##           Reference
## Prediction  Low  Med High
##       Low  1490  973  875
##       Med    15   29   25
##       High  422  479  744

Spider chart

# Create a data frame with all metrics
metrics_df <- data.frame(
  Model = c("Random Forest", "Multinomial", "Naive Bayes", "SVM", "Ensemble"),
  Accuracy = c(metrics_rf$Accuracy, metrics_multinom$Accuracy, metrics_nb$Accuracy, metrics_svm$Accuracy, metrics_ensemble$Accuracy),
  Precision = c(metrics_rf$Precision, metrics_multinom$Precision, metrics_nb$Precision, metrics_svm$Precision, metrics_ensemble$Precision),
  Recall = c(metrics_rf$Recall, metrics_multinom$Recall, metrics_nb$Recall, metrics_svm$Recall, metrics_ensemble$Recall),
  F1_Score = c(metrics_rf$F1_Score, metrics_multinom$F1_Score, metrics_nb$F1_Score, metrics_svm$F1_Score, metrics_ensemble$F1_Score)
)

# Display the metrics data frame
print(metrics_df)
##           Model  Accuracy Precision    Recall  F1_Score
## 1 Random Forest 0.4412114 0.4307740 0.4165142 0.3985085
## 2   Multinomial 0.3814331 0.3814331 0.3333333 0.5522281
## 3   Naive Bayes 0.3820269 0.7105376 0.3340383 0.1858645
## 4           SVM 0.4422011 0.4078497 0.4180312 0.3585790
## 5      Ensemble 0.4479414 0.4396482 0.4151196 0.3519461
# Find the max and min values for each metric
max_values <- rep(1, 4)  # Since all metrics are between 0 and 1
min_values <- rep(0, 4)

# Prepare data for radar chart
radar_data <- rbind(max_values, min_values, metrics_df[,-1])

# Set row names
rownames(radar_data) <- c("Max", "Min", metrics_df$Model)

# Define colors for each model
colors_border <- c("red", "blue", "green", "purple", "orange")
colors_in <- adjustcolor(colors_border, alpha.f = 0.1)

# Plot the radar chart
radarchart(
  radar_data,
  axistype = 1,
  pcol = colors_border,
  pfcol = colors_in,
  plwd = 2,
  plty = 1,
  # Customize the grid
  cglcol = "grey",
  cglty = 1,
  axislabcol = "grey",
  caxislabels = seq(0, 1, 0.2),
  cglwd = 0.8,
  # Customize variable labels
  vlcex = 0.8,
  title = "Model Performance Comparison"
)

# Add a legend
legend(
  x = "topright",
  legend = metrics_df$Model,
  bty = "n",
  pch = 20,
  col = colors_border,
  text.col = "black",
  cex = 0.8,
  pt.cex = 1.5
)

Importance feature plot RF RQ2

# Load necessary libraries
library(ggplot2)
library(caret)
library(dplyr)

# Extract variable importance and assign feature name explicitly
importance_rf_df <- varImp(rfRQ2, scale = FALSE)$importance %>%
  mutate(Feature = "normalized_records") %>%
  select(Feature, Overall)

# Plot feature importance
ggplot(importance_rf_df, aes(x = reorder(Feature, Overall), y = Overall)) +
  geom_bar(stat = 'identity', fill = "steelblue") +
  coord_flip() +
  labs(title = "Feature Importance - Random Forest Model (RQ2 Model 1)",
       x = "Feature",
       y = "Importance (Overall)") +
  theme_minimal() +
  theme(
    plot.title = element_text(hjust = 0.5, size = 16, face = "bold"),
    axis.title = element_text(size = 14),
    axis.text = element_text(size = 12)
  )

Feauture importance plot Multinomial

# Load necessary libraries
library(ggplot2)
library(caret)
library(dplyr)

# Check if the model was successfully trained
if (!is.null(multinomRQ2)) {
  
  # Extract variable importance using varImp
  importance_multinom <- varImp(multinomRQ2, scale = FALSE)
  
  # Convert the importance object to a data frame
  importance_df <- importance_multinom$importance
  
  # Add the feature names as a separate column
  importance_df <- importance_df %>%
    mutate(Feature = rownames(.)) %>%
    # Reshape the data for plotting (long format)
    pivot_longer(
      cols = -Feature,
      names_to = "Class",
      values_to = "Importance"
    )
  
  # Plot feature importance using ggplot2
  ggplot(importance_df, aes(x = reorder(Feature, Importance), y = Importance, fill = Class)) +
    geom_bar(stat = 'identity', position = position_dodge()) +
    coord_flip() +
    labs(title = "Feature Importance - Multinomial Logistic Regression Model",
         x = "Feature",
         y = "Importance") +
    theme_minimal() +
    theme(
      plot.title = element_text(hjust = 0.5, size = 16, face = "bold"),
      axis.title = element_text(size = 14),
      axis.text = element_text(size = 12),
      legend.title = element_text(size = 12),
      legend.text = element_text(size = 10)
    )
  
} else {
  cat("Multinomial Logistic Regression model is not available.\n")
}

Roc Curve for each class

# Load necessary libraries
library(pROC)
library(ggplot2)
library(dplyr)

# Check if the model was successfully trained
if (!is.null(multinomRQ2)) {
  
  # Predict probabilities on validation data
  pred_multinom_probs <- predict(multinomRQ2, newdata = val_data_RQ2, type = "prob")
  
  # Define the classes
  classes <- levels(train_data_RQ2$attack_severity_label)
  
  # Initialize lists to store ROC objects
  roc_multinom_list <- list()
  
  # Generate ROC curves for each class
  for (class in classes) {
    # Binary response: 1 for the current class, 0 otherwise
    binary_response <- ifelse(val_data_RQ2$attack_severity_label == class, 1, 0)
    
    # Generate ROC curve using pROC::roc to avoid conflicts
    roc_obj <- pROC::roc(response = binary_response,
                         predictor = pred_multinom_probs[, class],
                         levels = c(0, 1),
                         direction = "<")  # Adjust direction based on your model's output
    
    # Store ROC object
    roc_multinom_list[[class]] <- roc_obj
  }
  
  # Plot ROC curves for all classes using pROC::ggroc
  pROC::ggroc(roc_multinom_list, aes = c("color")) +
    labs(title = "ROC Curves for Each Class - Multinomial Logistic Regression",
         x = "False Positive Rate",
         y = "True Positive Rate",
         color = "Class") +
    theme_minimal() +
    theme(
      plot.title = element_text(hjust = 0.5, size = 16, face = "bold"),
      axis.title = element_text(size = 14),
      axis.text = element_text(size = 12),
      legend.title = element_text(size = 12),
      legend.text = element_text(size = 10)
    )
  
} else {
  cat("Multinomial Logistic Regression model is not available.\n")
}

Feature importance - NB

# Feature importance not available for Naive Bayes with a single predictor
# varImp is not supported for the naivebayes package in this configuration
cat("Feature importance plot not available for Naive Bayes (RQ2 Model 1).\n")
## Feature importance plot not available for Naive Bayes (RQ2 Model 1).
cat("Model uses a single predictor: normalized_records\n")
## Model uses a single predictor: normalized_records

Confusion Matrix heatmap

# Check if the model was successfully trained
if (!is.null(nbRQ2)) {
  
  # Predict classes on validation data
  pred_nb <- predict(nbRQ2, newdata = val_data_RQ2)
  
  # Confusion matrix
  cm_nb <- confusionMatrix(pred_nb, val_data_RQ2$attack_severity_label)
  
  # Convert the confusion matrix to a data frame
  cm_table <- as.data.frame(cm_nb$table)
  
  # Plot confusion matrix heatmap
  ggplot(cm_table, aes(x = Reference, y = Prediction, fill = Freq)) +
    geom_tile(color = "white") +
    scale_fill_gradient(low = "white", high = "steelblue") +
    geom_text(aes(label = Freq), color = "black", size = 5) +
    labs(title = "Confusion Matrix Heatmap - Naive Bayes Model (RQ2)",
         x = "Actual Class",
         y = "Predicted Class",
         fill = "Frequency") +
    theme_minimal() +
    theme(
      plot.title = element_text(hjust = 0.5, size = 16, face = "bold"),
      axis.title = element_text(size = 14),
      axis.text = element_text(size = 12),
      legend.title = element_text(size = 12),
      legend.text = element_text(size = 10)
    )
  
} else {
  cat("Naive Bayes model is not available.\n")
}

ROC AUC Plot

# Load necessary libraries
library(pROC)
library(ggplot2)
library(dplyr)

# Check if the model was successfully trained
if (!is.null(nbRQ2)) {
  
  # Predict probabilities on validation data
  pred_nb_probs <- predict(nbRQ2, newdata = val_data_RQ2, type = "prob")
  
  # Ensure 'attack_severity_label' is a factor with the same levels as training data
  val_data_RQ2$attack_severity_label <- factor(val_data_RQ2$attack_severity_label, 
                                               levels = levels(train_data_RQ2$attack_severity_label))
  
  # Define the classes
  classes <- levels(train_data_RQ2$attack_severity_label)
  
  # Initialize lists to store ROC objects
  roc_nb_list <- list()
  
  # Generate ROC curves for each class
  for (class in classes) {
    # Check if the class exists in the validation data
    if (!(class %in% val_data_RQ2$attack_severity_label)) {
      warning(paste("Class", class, "not present in validation data. Skipping ROC computation for this class."))
      next
    }
    
    # Binary response: 1 for the current class, 0 otherwise
    binary_response <- ifelse(val_data_RQ2$attack_severity_label == class, 1, 0)
    
    # Ensure there are both positive and negative instances
    if (length(unique(binary_response)) < 2) {
      warning(paste("Only one class present for", class, ". ROC curve cannot be computed."))
      next
    }
    
    # Compute ROC curve using pROC::roc to avoid conflicts
    roc_obj <- pROC::roc(
      response = binary_response,
      predictor = pred_nb_probs[[class]],
      levels = c(0, 1),
      direction = "<"
    )
    
    # Store the ROC object in the list with the class name
    roc_nb_list[[class]] <- roc_obj
  }
  
  # Initialize an empty data frame to hold ROC data
  roc_df <- data.frame()
  
  # Compile ROC data for plotting
  for (class in names(roc_nb_list)) {
    roc_obj <- roc_nb_list[[class]]
    
    temp_df <- data.frame(
      FPR = 1 - roc_obj$specificities,
      TPR = roc_obj$sensitivities,
      Class = class,
      AUC = rep(pROC::auc(roc_obj), length(roc_obj$specificities))
    )
    
    roc_df <- rbind(roc_df, temp_df)
  }
  
  # Check if roc_df is not empty
  if (nrow(roc_df) == 0) {
    stop("No ROC curves were computed. Please check if classes are present in the validation data.")
  }
  
  # Plot ROC curves for all classes
  ggplot(data = roc_df, aes(x = FPR, y = TPR, color = Class)) +
    geom_line(size = 1) +
    geom_abline(intercept = 0, slope = 1, linetype = "dashed", color = "grey") +
    labs(title = "ROC Curves for Each Class - Naive Bayes Model (RQ2)",
         x = "False Positive Rate (1 - Specificity)",
         y = "True Positive Rate (Sensitivity)",
         color = "Class") +
    theme_minimal() +
    theme(
      plot.title = element_text(hjust = 0.5, size = 16, face = "bold"),
      axis.title = element_text(size = 14),
      axis.text = element_text(size = 12),
      legend.title = element_text(size = 12),
      legend.text = element_text(size = 10)
    ) +
    scale_color_manual(values = rainbow(length(classes)),
                       labels = sapply(classes, function(cls) {
                         if (cls %in% names(roc_nb_list)) {
                           auc_val <- round(pROC::auc(roc_nb_list[[cls]]), 2)
                           paste0(cls, " (AUC = ", auc_val, ")")
                         } else {
                           paste0(cls, " (AUC = NA)")
                         }
                       }))
  
} else {
  cat("Naive Bayes model is not available.\n")
}

Feature of Importance - SVM

# Feature importance not available for SVM with a single predictor
# varImp is not supported for svmRadial in this configuration
cat("Feature importance plot not available for SVM (RQ2 Model 1).\n")
## Feature importance plot not available for SVM (RQ2 Model 1).
cat("Model uses a single predictor: normalized_records\n")
## Model uses a single predictor: normalized_records

Confusion Heat Map

# Check if the model was successfully trained
if (!is.null(svmRQ2)) {
  
  # Predict classes on validation data
  pred_svm <- predict(svmRQ2, newdata = val_data_RQ2)
  
  # Confusion matrix
  cm_svm <- confusionMatrix(pred_svm, val_data_RQ2$attack_severity_label)
  
  # Convert the confusion matrix to a data frame
  cm_table <- as.data.frame(cm_svm$table)
  
  # Plot confusion matrix heatmap
  ggplot(cm_table, aes(x = Reference, y = Prediction, fill = Freq)) +
    geom_tile(color = "white") +
    scale_fill_gradient(low = "white", high = "steelblue") +
    geom_text(aes(label = Freq), color = "black", size = 5) +
    labs(title = "Confusion Matrix Heatmap - SVM Model (RQ2)",
         x = "Actual Class",
         y = "Predicted Class",
         fill = "Frequency") +
    theme_minimal() +
    theme(
      plot.title = element_text(hjust = 0.5, size = 16, face = "bold"),
      axis.title = element_text(size = 14),
      axis.text = element_text(size = 12),
      legend.title = element_text(size = 12),
      legend.text = element_text(size = 10)
    )
  
} else {
  cat("SVM model is not available.\n")
}

Confusion heatmap - Ensemble

# Compute ensemble confusion matrix object for heatmap
cm_ensemble <- confusionMatrix(ensemble_prediction,
                               test_data_RQ2$attack_severity_label)

# Check if the ensemble model was successfully created
if (exists("cm_ensemble")) {
  
  # Convert the confusion matrix to a data frame
  cm_table <- as.data.frame(cm_ensemble$table)
  
  # Rename columns for clarity
  colnames(cm_table) <- c("Actual", "Predicted", "Frequency")
  
  # Plot confusion matrix heatmap
  ggplot(cm_table, aes(x = Actual, y = Predicted, fill = Frequency)) +
    geom_tile(color = "white") +
    scale_fill_gradient(low = "white", high = "steelblue") +
    geom_text(aes(label = Frequency), color = "black", size = 5) +
    labs(title = "Confusion Matrix Heatmap - Ensemble Model (RQ2 Model 1)",
         x = "Actual Class",
         y = "Predicted Class",
         fill = "Frequency") +
    theme_minimal() +
    theme(
      plot.title = element_text(hjust = 0.5, size = 16, face = "bold"),
      axis.title = element_text(size = 14),
      axis.text = element_text(size = 12),
      legend.title = element_text(size = 12),
      legend.text = element_text(size = 10)
    )
  
} else {
  cat("Confusion matrix for the ensemble model is not available.\n")
}

Evaluations metrics bar plot

# Load necessary libraries
library(ggplot2)
library(dplyr)
library(tidyr)

# --- Ensemble Model ---

# Use already-computed ensemble metrics from the auto-selection ensemble chunk
# (RF + SVM + NB — top 3 by accuracy)
if (!exists("metrics_ensemble")) {
  stop("Run the ensemble chunk first before running this plot.")
}

# --- Plotting Ensemble Metrics ---

# Check if the ensemble evaluation metrics are available
if (exists("metrics_ensemble")) {
  
  # Extract metrics from metrics_ensemble
  accuracy <- metrics_ensemble$Accuracy
  precision <- metrics_ensemble$Precision
  recall <- metrics_ensemble$Recall
  f1_score <- metrics_ensemble$F1_Score
  
  # Create a data frame for plotting
  metrics_df <- data.frame(
    Metric = c("Accuracy", "Precision", "Recall", "F1-Score"),
    Value = c(accuracy, precision, recall, f1_score)
  )
  
  # Plot evaluation metrics
  ggplot(metrics_df, aes(x = Metric, y = Value, fill = Metric)) +
    geom_bar(stat = "identity", width = 0.6) +
    geom_text(aes(label = round(Value, 3)), vjust = -0.5, size = 5) +
    ylim(0, 1) +
    labs(title = "Ensemble Model Evaluation Metrics",
         x = "Metric",
         y = "Value") +
    theme_minimal() +
    theme(
      plot.title = element_text(hjust = 0.5, size = 16, face = "bold"),
      axis.title = element_text(size = 14),
      axis.text = element_text(size = 12),
      legend.position = "none"
    ) +
    scale_fill_brewer(palette = "Set2")
  
} else {
  cat("Evaluation metrics for the ensemble model are not available.\n")
}

Comparison of Metrics Between Individual Models and Ensemble

# Load necessary libraries
library(ggplot2)
library(dplyr)
library(tidyr)

# Create the data frame with live metrics from model objects
metrics_df <- data.frame(
  Model    = c("Random Forest", "Multinomial", "Naive Bayes", "SVM", "Ensemble"),
  Accuracy = c(metrics_rf$Accuracy, metrics_multinom$Accuracy, metrics_nb$Accuracy, 
               metrics_svm$Accuracy, metrics_ensemble$Accuracy),
  Precision = c(metrics_rf$Precision, metrics_multinom$Precision, metrics_nb$Precision, 
                metrics_svm$Precision, metrics_ensemble$Precision),
  Recall   = c(metrics_rf$Recall, metrics_multinom$Recall, metrics_nb$Recall, 
               metrics_svm$Recall, metrics_ensemble$Recall),
  F1_Score = c(metrics_rf$F1_Score, metrics_multinom$F1_Score, metrics_nb$F1_Score, 
               metrics_svm$F1_Score, metrics_ensemble$F1_Score)
)

# Reshape the data to long format
metrics_long <- metrics_df %>%
  pivot_longer(
    cols = -Model,
    names_to = "Metric",
    values_to = "Value"
  )

# Set a consistent color palette
palette_colors <- RColorBrewer::brewer.pal(n = 5, name = "Set1")

# Create the bar plot
ggplot(metrics_long, aes(x = Metric, y = Value, fill = Model)) +
  geom_bar(stat = "identity", position = position_dodge(width = 0.8)) +
  geom_text(aes(label = round(Value, 3)), 
            position = position_dodge(width = 0.8), 
            vjust = -0.3, 
            size = 3.5) +
  scale_fill_manual(values = palette_colors) +
  labs(title = "Comparison of Evaluation Metrics Between Models",
       x = "Metric",
       y = "Value",
       fill = "Model") +
  ylim(0, 1.1) +  # Extend y-axis to accommodate text labels
  theme_minimal() +
  theme(
    plot.title = element_text(hjust = 0.5, size = 16, face = "bold"),
    axis.title = element_text(size = 14),
    axis.text = element_text(size = 12),
    legend.title = element_text(size = 13),
    legend.text = element_text(size = 11)
  )

# Optional: Save the plot
# ggsave("model_comparison_metrics.png", width = 10, height = 6)

Export results

# ── EXPORT RESULTS — RQ2 Model 1 ───────────────────────────────────────────────

# Create output folder if it doesn't exist
output_dir <- "Data/Outputs/RQ2Model1"
if (!dir.exists(output_dir)) dir.create(output_dir, recursive = TRUE)

# --- Export metrics to CSV ---
results_table_m1 <- data.frame(
  Metric        = c("Accuracy", "Precision", "Recall", "F1 Score"),
  Random_Forest = c(metrics_rf$Accuracy, metrics_rf$Precision,
                    metrics_rf$Recall, metrics_rf$F1_Score),
  Multinomial   = c(metrics_multinom$Accuracy, metrics_multinom$Precision,
                    metrics_multinom$Recall, metrics_multinom$F1_Score),
  Naive_Bayes   = c(metrics_nb$Accuracy, metrics_nb$Precision,
                    metrics_nb$Recall, metrics_nb$F1_Score),
  SVM           = c(metrics_svm$Accuracy, metrics_svm$Precision,
                    metrics_svm$Recall, metrics_svm$F1_Score),
  Ensemble      = c(metrics_ensemble$Accuracy, metrics_ensemble$Precision,
                    metrics_ensemble$Recall, metrics_ensemble$F1_Score)
)

# Round to 4 decimal places
results_table_m1[, 2:6] <- round(results_table_m1[, 2:6], 4)

# Save
write.csv(results_table_m1, file.path(output_dir, "RQ2_M1_Results.csv"),
          row.names = FALSE)

cat("Files saved:\n")
## Files saved:
cat(paste0("- ", output_dir, "/RQ2_M1_Results.csv\n"))
## - Data/Outputs/RQ2Model1/RQ2_M1_Results.csv
# --- Save plots ---

# Metrics comparison bar chart
p_metrics_comparison <- ggplot(metrics_long, aes(x = Metric, y = Value, fill = Model)) +
  geom_bar(stat = "identity", position = position_dodge(width = 0.8)) +
  geom_text(aes(label = round(Value, 3)),
            position = position_dodge(width = 0.8), vjust = -0.3, size = 3.5) +
  scale_fill_manual(values = RColorBrewer::brewer.pal(n = 5, name = "Set1")) +
  labs(title = "Comparison of Evaluation Metrics Between Models",
       x = "Metric", y = "Value", fill = "Model") +
  ylim(0, 1.1) +
  theme_minimal() +
  theme(plot.title = element_text(hjust = 0.5, size = 16, face = "bold"),
        axis.title = element_text(size = 14),
        axis.text  = element_text(size = 12))
ggsave(file.path(output_dir, "RQ2_M1_Metrics_Comparison.png"),
       plot = p_metrics_comparison, width = 10, height = 6, dpi = 300)

# RF feature importance
importance_rf_df <- varImp(rfRQ2, scale = FALSE)$importance %>%
  mutate(Feature = rownames(.)) %>%
  select(Feature, Overall)
p_rf_importance <- ggplot(importance_rf_df,
                          aes(x = reorder(Feature, Overall), y = Overall)) +
  geom_bar(stat = "identity", fill = "steelblue") +
  coord_flip() +
  labs(title = "Feature Importance - Random Forest (RQ2 Model 1)",
       x = "Feature", y = "Importance") +
  theme_minimal() +
  theme(plot.title = element_text(hjust = 0.5, size = 16, face = "bold"))
ggsave(file.path(output_dir, "RQ2_M1_RF_Feature_Importance.png"),
       plot = p_rf_importance, width = 8, height = 5, dpi = 300)

# Recompute test set predictions to ensure correct length
# (visualization chunks may have overwritten these with val set predictions)
pred_rf_test      <- predict(rfRQ2,       newdata = test_data_RQ2)
pred_multinom_test <- predict(multinomRQ2, newdata = test_data_RQ2)
pred_nb_test      <- predict(nbRQ2,       newdata = test_data_RQ2)
pred_svm_test     <- predict(svmRQ2,      newdata = test_data_RQ2)

# Confusion matrices — one PNG per model
cm_models <- list(
  "RandomForest" = list(pred = pred_rf_test,       true = test_data_RQ2$attack_severity_label),
  "Multinomial"  = list(pred = pred_multinom_test,  true = test_data_RQ2$attack_severity_label),
  "NaiveBayes"   = list(pred = pred_nb_test,        true = test_data_RQ2$attack_severity_label),
  "SVM"          = list(pred = pred_svm_test,       true = test_data_RQ2$attack_severity_label),
  "Ensemble"     = list(pred = ensemble_prediction, true = test_data_RQ2$attack_severity_label)
)

for (model_name in names(cm_models)) {
  cm <- confusionMatrix(cm_models[[model_name]]$pred, cm_models[[model_name]]$true)
  cm_df <- as.data.frame(cm$table)
  p_cm <- ggplot(cm_df, aes(x = Reference, y = Prediction, fill = Freq)) +
    geom_tile(color = "white") +
    scale_fill_gradient(low = "white", high = "steelblue") +
    geom_text(aes(label = Freq), color = "black", size = 5) +
    labs(title = paste("Confusion Matrix -", model_name),
         x = "Actual", y = "Predicted", fill = "Frequency") +
    theme_minimal() +
    theme(plot.title = element_text(hjust = 0.5, size = 14, face = "bold"))
  ggsave(file.path(output_dir, paste0("RQ2_M1_CM_", model_name, ".png")),
         plot = p_cm, width = 6, height = 5, dpi = 300)
}

cat("All plots saved to", output_dir, "\n")
## All plots saved to Data/Outputs/RQ2Model1
# ── EXPORT — Radar Chart ───────────────────────────────────────────────────────
png(file.path(output_dir, "RQ2_M1_Radar_Chart.png"), width = 800, height = 600)
radarchart(
  radar_data, axistype = 1,
  pcol = colors_border, pfcol = colors_in, plwd = 2, plty = 1,
  cglcol = "grey", cglty = 1, axislabcol = "grey",
  caxislabels = seq(0, 1, 0.2), cglwd = 0.8,
  vlcex = 0.8,
  title = "Model Performance Comparison"
)
legend(x = "topright", legend = metrics_df$Model, bty = "n",
       pch = 20, col = colors_border, text.col = "black", cex = 0.8, pt.cex = 1.5)
dev.off()
## quartz_off_screen 
##                 2
cat("Radar chart saved to", output_dir, "\n")
## Radar chart saved to Data/Outputs/RQ2Model1
# ── EXPORT — ROC Curves ────────────────────────────────────────────────────────

# Multinomial ROC
if (exists("roc_multinom_list")) {
  p_roc_multinom <- pROC::ggroc(roc_multinom_list, aes = c("color")) +
    labs(title = "ROC Curves - Multinomial Logistic Regression (RQ2 Model 1)",
         x = "False Positive Rate", y = "True Positive Rate", color = "Class") +
    theme_minimal() +
    theme(plot.title = element_text(hjust = 0.5, size = 14, face = "bold"))
  ggsave(file.path(output_dir, "RQ2_M1_ROC_Multinomial.png"),
         plot = p_roc_multinom, width = 7, height = 5, dpi = 300)
  cat("Multinomial ROC saved\n")
} else {
  cat("Multinomial ROC list not found — run the ROC chunk first\n")
}
## Multinomial ROC saved
# Naive Bayes ROC
if (exists("roc_nb_list")) {
  p_roc_nb <- pROC::ggroc(roc_nb_list, aes = c("color")) +
    labs(title = "ROC Curves - Naive Bayes (RQ2 Model 1)",
         x = "False Positive Rate", y = "True Positive Rate", color = "Class") +
    theme_minimal() +
    theme(plot.title = element_text(hjust = 0.5, size = 14, face = "bold"))
  ggsave(file.path(output_dir, "RQ2_M1_ROC_NaiveBayes.png"),
         plot = p_roc_nb, width = 7, height = 5, dpi = 300)
  cat("Naive Bayes ROC saved\n")
} else {
  cat("Naive Bayes ROC list not found — run the ROC chunk first\n")
}
## Naive Bayes ROC saved
cat("All ROC curves saved to", output_dir, "\n")
## All ROC curves saved to Data/Outputs/RQ2Model1

Model 2

# Load necessary libraries
library(caret)
library(nnet)        # For Multinomial Regression and Perceptron
library(xgboost)     # For XGBoost
library(randomForest)
library(tidyverse)
library(MLmetrics)
library(pROC)

# Use the same datasets and label built in the RQ2 data prep chunk
# train_data_RQ2, val_data_RQ2, test_data_RQ2 already exist with attack_severity_label

# Ensure factor consistency across splits
train_data_RQ2 <- train_data_RQ2 %>% mutate_if(is.character, as.factor)
val_data_RQ2   <- val_data_RQ2   %>% mutate_if(is.character, as.factor)
test_data_RQ2  <- test_data_RQ2  %>% mutate_if(is.character, as.factor)

# Confirm attack_severity_label levels are consistent
val_data_RQ2$attack_severity_label  <- factor(val_data_RQ2$attack_severity_label,
                                              levels = levels(train_data_RQ2$attack_severity_label))
test_data_RQ2$attack_severity_label <- factor(test_data_RQ2$attack_severity_label,
                                              levels = levels(train_data_RQ2$attack_severity_label))

# Define the formula
formula_m2 <- attack_severity_label ~ normalized_records

Multinomial Logistic Regression Model

# Multinomial Logistic Regression
set.seed(123)
multinom_model_rq2 <- multinom(formula_m2, data = train_data_RQ2)
## # weights:  9 (4 variable)
## initial  value 19422.366651 
## iter  10 value 19322.320508
## iter  20 value 19321.749638
## iter  20 value 19321.749554
## iter  30 value 19321.679682
## iter  30 value 19321.679677
## iter  30 value 19321.679497
## final  value 19321.679497 
## converged
# Predict on test data
pred_multinom_m2 <- predict(multinom_model_rq2, newdata = test_data_RQ2)

# Calculate metrics
metrics_multinom_m2 <- calculate_metrics(pred_multinom_m2, 
                                         test_data_RQ2$attack_severity_label)
cat("Multinomial Metrics (Model 2):\n")
## Multinomial Metrics (Model 2):
print(metrics_multinom_m2)
## $Accuracy
## [1] 0.3814331
## 
## $Precision
## [1] 0.3814331
## 
## $Recall
## [1] 0.3333333
## 
## $F1_Score
## [1] 0.5522281

Feature of importance

# Load necessary libraries
library(caret)
library(ggplot2)
library(pROC)
library(dplyr)
library(tidyr)
library(reshape2)
library(nnet)  # For multinomial logistic regression

# Ensure the model is not NULL
if (!is.null(multinom_model_rq2)) {
  
  # Extract coefficients
  coefs <- coef(multinom_model_rq2)
  
  # Check the structure of coefficients
  print(coefs)
  
  # Convert coefficients to a data frame
  coefs_df <- as.data.frame(coefs)
  
  # Add feature names as a column
  coefs_df <- coefs_df %>%
    mutate(Feature = rownames(coefs_df))
  
  # Reshape the data to long format for plotting
  coefs_long <- coefs_df %>%
    pivot_longer(
      cols = -Feature,
      names_to = "Class",
      values_to = "Coefficient"
    )
  
  # Calculate absolute coefficients for importance
  coefs_long <- coefs_long %>%
    mutate(Importance = abs(Coefficient))
  
  # Plot Feature Importance
  ggplot(coefs_long, aes(x = reorder(Feature, Importance), y = Importance, fill = Class)) +
    geom_bar(stat = 'identity', position = position_dodge()) +
    coord_flip() +
    labs(title = "Feature Importance - Multinomial Logistic Regression (RQ2)",
         x = "Feature",
         y = "Absolute Coefficient") +
    theme_minimal() +
    theme(
      plot.title = element_text(hjust = 0.5, size = 16, face = "bold"),
      axis.title = element_text(size = 14),
      axis.text = element_text(size = 12),
      legend.title = element_text(size = 12),
      legend.text = element_text(size = 10)
    )
  
} else {
  cat("Multinomial Logistic Regression model is not available.\n")
}
##      (Intercept) normalized_records
## Med   -0.2468974          -4.447262
## High  -0.1164133         -31.175434

# Compute mean absolute coefficient per feature
feature_importance <- coefs_long %>%
  group_by(Feature) %>%
  summarize(Mean_Importance = mean(Importance, na.rm = TRUE)) %>%
  arrange(desc(Mean_Importance))

# Plot Average Feature Importance
ggplot(feature_importance, aes(x = reorder(Feature, Mean_Importance), y = Mean_Importance)) +
  geom_bar(stat = 'identity', fill = "steelblue") +
  coord_flip() +
  labs(title = "Average Feature Importance - Multinomial Logistic Regression (RQ2)",
       x = "Feature",
       y = "Mean Absolute Coefficient") +
  theme_minimal() +
  theme(
    plot.title = element_text(hjust = 0.5, size = 16, face = "bold"),
    axis.title = element_text(size = 14),
    axis.text = element_text(size = 12)
  )

Confusion matrix heatmap

# Compute confusion matrix for Multinomial (Model 2)
cm_multinom_m2 <- confusionMatrix(pred_multinom_m2, 
                                   test_data_RQ2$attack_severity_label)
print("Multinomial Regression (Model 2) Confusion Matrix:")
## [1] "Multinomial Regression (Model 2) Confusion Matrix:"
print(cm_multinom_m2$table)
##           Reference
## Prediction  Low  Med High
##       Low  1927 1481 1644
##       Med     0    0    0
##       High    0    0    0
# Confusion matrix heatmap - Multinomial Model 2
if (!is.null(cm_multinom_m2)) {
  
  cm_table <- as.data.frame(cm_multinom_m2$table)
  colnames(cm_table) <- c("Actual", "Predicted", "Frequency")
  
  ggplot(cm_table, aes(x = Actual, y = Predicted, fill = Frequency)) +
    geom_tile(color = "white") +
    scale_fill_gradient(low = "white", high = "steelblue") +
    geom_text(aes(label = Frequency), color = "black", size = 5) +
    labs(title = "Confusion Matrix Heatmap - Multinomial Logistic Regression (RQ2 Model 2)",
         x = "Actual Class",
         y = "Predicted Class",
         fill = "Frequency") +
    theme_minimal() +
    theme(
      plot.title = element_text(hjust = 0.5, size = 16, face = "bold"),
      axis.title = element_text(size = 14),
      axis.text = element_text(size = 12),
      legend.title = element_text(size = 12),
      legend.text = element_text(size = 10)
    )
  
} else {
  cat("Confusion matrix for Multinomial (Model 2) is not available.\n")
}

Perceptron Model (Single Layer Neural Network)

# Load necessary libraries
library(caret)
library(nnet)

# Perceptron (single layer neural network)
set.seed(123)
perc_model_rq2 <- train(
  formula_m2,
  data = train_data_RQ2,
  method = 'nnet',
  linout = FALSE,
  trace = FALSE,
  maxit = 200
)

# Predict on test data
pred_perc_m2 <- predict(perc_model_rq2, newdata = test_data_RQ2)

# Calculate metrics
metrics_perc_m2 <- calculate_metrics(pred_perc_m2,
                                     test_data_RQ2$attack_severity_label)
cat("Perceptron Metrics (Model 2):\n")
## Perceptron Metrics (Model 2):
print(metrics_perc_m2)
## $Accuracy
## [1] 0.3812352
## 
## $Precision
## [1] 0.390599
## 
## $Recall
## [1] 0.3333687
## 
## $F1_Score
## [1] 0.2784756

Feature Importance

# Load necessary libraries
library(caret)
library(ggplot2)
library(dplyr)
library(tidyr)  # For pivot_longer()

# Check if the perceptron model was successfully trained
if (!is.null(perc_model_rq2)) {
  
  # Extract variable importance using varImp
  importance_perc <- varImp(perc_model_rq2, scale = FALSE)
  
  # Inspect the importance object
  print(importance_perc)
  
  # Convert the importance object to a data frame
  importance_df <- as.data.frame(importance_perc$importance)
  
  # Add feature names as a column
  importance_df <- importance_df %>%
    mutate(Feature = rownames(.))
  
  # Determine if it's a multi-class or single-class model
  num_classes <- ncol(importance_df) - 1  # Subtracting 'Feature' column
  
  if (num_classes > 1) {  # Multi-class importance
    importance_long <- importance_df %>%
      pivot_longer(
        cols = -Feature,
        names_to = "Class",
        values_to = "Importance"
      )
    
    # Plot Feature Importance for Multi-class
    ggplot(importance_long, aes(x = reorder(Feature, Importance), y = Importance, fill = Class)) +
      geom_bar(stat = 'identity', position = position_dodge()) +
      coord_flip() +
      labs(title = "Feature Importance - Perceptron (RQ2)",
           x = "Feature",
           y = "Importance",
           fill = "Class") +
      theme_minimal() +
      theme(
        plot.title = element_text(hjust = 0.5, size = 16, face = "bold"),
        axis.title = element_text(size = 14),
        axis.text = element_text(size = 12),
        legend.title = element_text(size = 12),
        legend.text = element_text(size = 10)
      )
    
  } else {  # Single-class importance
    importance_long <- importance_df %>%
      rename(Importance = Overall)  # Assuming the single-class column is named 'Overall'
    
    # Plot Feature Importance for Single-class
    ggplot(importance_long, aes(x = reorder(Feature, Importance), y = Importance)) +
      geom_bar(stat = 'identity', fill = "steelblue") +
      coord_flip() +
      labs(title = "Feature Importance - Perceptron (RQ2)",
           x = "Feature",
           y = "Importance") +
      theme_minimal() +
      theme(
        plot.title = element_text(hjust = 0.5, size = 16, face = "bold"),
        axis.title = element_text(size = 14),
        axis.text = element_text(size = 12)
      )
  }
  
} else {
  cat("Perceptron model is not available.\n")
}
## nnet variable importance
## 
##   variables are sorted by maximum importance across the classes
##                    Overall Low Med High
## normalized_records     100 100 100  100

ROC Curves

# Load necessary libraries
library(pROC)
library(ggplot2)

# Check if the perceptron model was successfully trained
if (!is.null(perc_model_rq2)) {
  
  # Predict probabilities on validation data
  pred_perc_probs <- predict(perc_model_rq2, newdata = val_data_RQ2, type = "prob")
  
  # Define the classes
  classes <- levels(train_data_RQ2$attack_severity_label)
  
  # Initialize lists to store ROC objects
  roc_perc_list <- list()
  
  # Generate ROC curves for each class
  for (class in classes) {
    # Binary response: 1 for the current class, 0 otherwise
   binary_response <- ifelse(val_data_RQ2$attack_severity_label == class, 1, 0)
    
    # Ensure there are both positive and negative instances
    if (length(unique(binary_response)) < 2) {
      warning(paste("Only one class present for", class, ". ROC curve cannot be computed."))
      next
    }
    
    # Compute ROC curve using pROC::roc to avoid conflicts
    roc_obj <- pROC::roc(
      response = binary_response,
      predictor = pred_perc_probs[[class]],
      levels = c(0, 1),
      direction = "<"  # Adjust based on your model's output
    )
    
    # Store the ROC object in the list with the class name
    roc_perc_list[[class]] <- roc_obj
  }
  
  # Initialize an empty data frame to hold ROC data
  roc_df <- data.frame()
  
  # Compile ROC data for plotting
  for (class in names(roc_perc_list)) {
    roc_obj <- roc_perc_list[[class]]
    
    # Create a temporary data frame with FPR, TPR, Class, and AUC
    temp_df <- data.frame(
      FPR = 1 - roc_obj$specificities,
      TPR = roc_obj$sensitivities,
      Class = class,
      AUC = rep(pROC::auc(roc_obj), length(roc_obj$specificities))  # Replicate AUC to match vector lengths
    )
    
    # Append to the main ROC data frame
    roc_df <- rbind(roc_df, temp_df)
  }
  
  # Check if roc_df is not empty
  if (nrow(roc_df) == 0) {
    stop("No ROC curves were computed. Please check if classes are present in the validation data.")
  }
  
  # Plot ROC curves for all classes
  ggplot(data = roc_df, aes(x = FPR, y = TPR, color = Class)) +
    geom_line(size = 1) +
    geom_abline(intercept = 0, slope = 1, linetype = "dashed", color = "grey") +
    labs(title = "ROC Curves for Each Class - Perceptron (RQ2)",
         x = "False Positive Rate (1 - Specificity)",
         y = "True Positive Rate (Sensitivity)",
         color = "Class") +
    theme_minimal() +
    theme(
      plot.title = element_text(hjust = 0.5, size = 16, face = "bold"),
      axis.title = element_text(size = 14),
      axis.text = element_text(size = 12),
      legend.title = element_text(size = 12),
      legend.text = element_text(size = 10)
    ) +
    # Add AUC values to the legend labels
    scale_color_manual(values = rainbow(length(classes)),
                       labels = sapply(classes, function(cls) {
                         if (cls %in% names(roc_perc_list)) {
                           auc_val <- round(pROC::auc(roc_perc_list[[cls]]), 2)
                           paste0(cls, " (AUC = ", auc_val, ")")
                         } else {
                           paste0(cls, " (AUC = NA)")
                         }
                       }))
  
} else {
  cat("Perceptron model is not available.\n")
}

XGBoost Model

# Load necessary libraries
library(xgboost)
library(caret)

# Prepare data for XGBoost (requires numeric matrix input)
label_levels <- levels(train_data_RQ2$attack_severity_label)

train_labels <- as.numeric(train_data_RQ2$attack_severity_label) - 1
test_labels  <- as.numeric(test_data_RQ2$attack_severity_label) - 1

train_matrix <- xgb.DMatrix(
  data  = as.matrix(train_data_RQ2[, "normalized_records", drop = FALSE]),
  label = train_labels
)
test_matrix <- xgb.DMatrix(
  data  = as.matrix(test_data_RQ2[, "normalized_records", drop = FALSE]),
  label = test_labels
)

# Train XGBoost model using xgb.train()
set.seed(123)
xgb_model_rq2 <- xgb.train(
  params = list(
    objective   = "multi:softmax",
    num_class   = 3,
    eval_metric = "merror",
    eta         = 0.1,
    max_depth   = 6
  ),
  data    = train_matrix,
  nrounds = 100,
  verbose = 0
)

# Predict on test data
pred_xgb_raw <- predict(xgb_model_rq2, test_matrix)

# Convert numeric predictions back to factor labels
pred_xgb_m2 <- factor(label_levels[pred_xgb_raw + 1],
                      levels = label_levels)

# Calculate metrics
metrics_xgb_m2 <- calculate_metrics(pred_xgb_m2,
                                    test_data_RQ2$attack_severity_label)
cat("XGBoost Metrics (Model 2):\n")
## XGBoost Metrics (Model 2):
print(metrics_xgb_m2)
## $Accuracy
## [1] 0.4433888
## 
## $Precision
## [1] 0.4304098
## 
## $Recall
## [1] 0.4209801
## 
## $F1_Score
## [1] 0.3909973

Confusion Matrix

# Compute confusion matrix for XGBoost (Model 2)
cm_xgb_m2 <- confusionMatrix(pred_xgb_m2,
                              test_data_RQ2$attack_severity_label)

cat("\nConfusion Matrix for XGBoost Model (Model 2):\n")
## 
## Confusion Matrix for XGBoost Model (Model 2):
print(cm_xgb_m2$table)
##           Reference
## Prediction  Low  Med High
##       Low  1234  793  649
##       Med    88  159  148
##       High  605  529  847

Feature Importance plot

# Feature importance not available for XGBoost with a single predictor
# xgb.importance requires multiple features to compute gain/cover/frequency splits
cat("Feature importance plot not available for XGBoost (RQ2 Model 2).\n")
## Feature importance plot not available for XGBoost (RQ2 Model 2).
cat("Model uses a single predictor: normalized_records\n")
## Model uses a single predictor: normalized_records
# No feature importance data to plot — single predictor model
cat("No feature importance plot generated for XGBoost (RQ2 Model 2).\n")
## No feature importance plot generated for XGBoost (RQ2 Model 2).

Confusion Matrix Heatmap

# Confusion Matrix Heatmap - XGBoost (Model 2)
cm_table_xgb <- as.data.frame(cm_xgb_m2$table)
colnames(cm_table_xgb) <- c("Actual", "Predicted", "Frequency")

ggplot(cm_table_xgb, aes(x = Actual, y = Predicted, fill = Frequency)) +
  geom_tile(color = "white") +
  scale_fill_gradient(low = "white", high = "steelblue") +
  geom_text(aes(label = Frequency), color = "black", size = 5) +
  labs(title = "Confusion Matrix Heatmap - XGBoost (RQ2 Model 2)",
       x = "Actual Class",
       y = "Predicted Class",
       fill = "Frequency") +
  theme_minimal() +
  theme(
    plot.title = element_text(hjust = 0.5, size = 16, face = "bold"),
    axis.title = element_text(size = 14),
    axis.text = element_text(size = 12),
    legend.title = element_text(size = 12),
    legend.text = element_text(size = 10)
  )

Other performance metrics

# XGBoost Performance Metrics (Model 2)
cat("XGBoost Model Performance Metrics (Model 2):\n")
## XGBoost Model Performance Metrics (Model 2):
print(metrics_xgb_m2)
## $Accuracy
## [1] 0.4433888
## 
## $Precision
## [1] 0.4304098
## 
## $Recall
## [1] 0.4209801
## 
## $F1_Score
## [1] 0.3909973
cat("\nPer-class breakdown:\n")
## 
## Per-class breakdown:
print(cm_xgb_m2$byClass[, c("Precision", "Recall", "F1")])
##             Precision    Recall        F1
## Class: Low  0.4611360 0.6403736 0.5361721
## Class: Med  0.4025316 0.1073599 0.1695096
## Class: High 0.4275618 0.5152068 0.4673103

Random Forest Model

# Random Forest (Model 2)
set.seed(123)
rf_model_rq2 <- train(
  formula_m2,
  data       = train_data_RQ2,
  method     = 'rf',
  trControl  = trainControl(method = 'cv')
)
## Warning in randomForest.default(x, y, mtry = param$mtry, ...): invalid mtry:
## reset to within valid range
## Warning in randomForest.default(x, y, mtry = param$mtry, ...): invalid mtry:
## reset to within valid range
## Warning in randomForest.default(x, y, mtry = param$mtry, ...): invalid mtry:
## reset to within valid range
## Warning in randomForest.default(x, y, mtry = param$mtry, ...): invalid mtry:
## reset to within valid range
## Warning in randomForest.default(x, y, mtry = param$mtry, ...): invalid mtry:
## reset to within valid range
## Warning in randomForest.default(x, y, mtry = param$mtry, ...): invalid mtry:
## reset to within valid range
## Warning in randomForest.default(x, y, mtry = param$mtry, ...): invalid mtry:
## reset to within valid range
## Warning in randomForest.default(x, y, mtry = param$mtry, ...): invalid mtry:
## reset to within valid range
## Warning in randomForest.default(x, y, mtry = param$mtry, ...): invalid mtry:
## reset to within valid range
## Warning in randomForest.default(x, y, mtry = param$mtry, ...): invalid mtry:
## reset to within valid range
## Warning in randomForest.default(x, y, mtry = param$mtry, ...): invalid mtry:
## reset to within valid range
# Predict on test data
pred_rf_m2 <- predict(rf_model_rq2, newdata = test_data_RQ2)

# Calculate metrics
metrics_rf_m2 <- calculate_metrics(pred_rf_m2,
                                   test_data_RQ2$attack_severity_label)
cat("Random Forest Metrics (Model 2):\n")
## Random Forest Metrics (Model 2):
print(metrics_rf_m2)
## $Accuracy
## [1] 0.4378464
## 
## $Precision
## [1] 0.4276754
## 
## $Recall
## [1] 0.4136926
## 
## $F1_Score
## [1] 0.3966151
# Confusion matrix and performance metrics - Random Forest (Model 2)
cm_rf_m2 <- confusionMatrix(pred_rf_m2,
                             test_data_RQ2$attack_severity_label)

cat("Confusion Matrix - Random Forest (Model 2):\n")
## Confusion Matrix - Random Forest (Model 2):
print(cm_rf_m2$table)
##           Reference
## Prediction  Low  Med High
##       Low  1390  909  834
##       Med   234  295  283
##       High  303  277  527
cat("\nRandom Forest Performance Metrics (Model 2):\n")
## 
## Random Forest Performance Metrics (Model 2):
print(metrics_rf_m2)
## $Accuracy
## [1] 0.4378464
## 
## $Precision
## [1] 0.4276754
## 
## $Recall
## [1] 0.4136926
## 
## $F1_Score
## [1] 0.3966151
cat("\nPer-class breakdown:\n")
## 
## Per-class breakdown:
print(cm_rf_m2$byClass[, c("Precision", "Recall", "F1")])
##             Precision    Recall        F1
## Class: Low  0.4436642 0.7213285 0.5494071
## Class: Med  0.3633005 0.1991897 0.2573048
## Class: High 0.4760614 0.3205596 0.3831334

Plot Confusion Matrix Heatmap

# Convert confusion matrix to data frame
cm_table_rf <- as.data.frame(cm_rf_m2$table)
colnames(cm_table_rf) <- c("Actual", "Predicted", "Frequency")

# Plot Confusion Matrix Heatmap
ggplot(cm_table_rf, aes(x = Actual, y = Predicted, fill = Frequency)) +
  geom_tile(color = "white") +
  scale_fill_gradient(low = "white", high = "steelblue") +
  geom_text(aes(label = Frequency), color = "black", size = 5) +
  labs(title = "Confusion Matrix Heatmap - Random Forest (RQ2 Model 2)",
       x = "Actual Class",
       y = "Predicted Class",
       fill = "Frequency") +
  theme_minimal() +
  theme(
    plot.title = element_text(hjust = 0.5, size = 16, face = "bold"),
    axis.title = element_text(size = 14),
    axis.text = element_text(size = 12)
  )

Feature importance

# Extract variable importance with explicit feature name
importance_rf_m2_df <- varImp(rf_model_rq2, scale = FALSE)$importance %>%
  mutate(Feature = "normalized_records") %>%
  select(Feature, Overall)

# Plot feature importance
ggplot(importance_rf_m2_df, aes(x = reorder(Feature, Overall), y = Overall)) +
  geom_bar(stat = 'identity', fill = "steelblue") +
  coord_flip() +
  labs(title = "Feature Importance - Random Forest (RQ2 Model 2)",
       x = "Feature",
       y = "Importance") +
  theme_minimal() +
  theme(
    plot.title = element_text(hjust = 0.5, size = 16, face = "bold"),
    axis.title = element_text(size = 14),
    axis.text  = element_text(size = 12)
  )

Ensemble Model (Majority Voting)

# --- Ensemble Model (Best 3 by Accuracy) — Model 2 ---

# Collect all individual model metrics
all_model_metrics_m2 <- list(
  "Multinomial"   = metrics_multinom_m2,
  "Perceptron"    = metrics_perc_m2,
  "XGBoost"       = metrics_xgb_m2,
  "Random Forest" = metrics_rf_m2
)

# Rank models by accuracy and select top 3
accuracy_scores_m2 <- sapply(all_model_metrics_m2, function(m) m$Accuracy)
top3_names_m2 <- names(sort(accuracy_scores_m2, decreasing = TRUE))[1:3]
cat("Top 3 models selected for ensemble:", paste(top3_names_m2, collapse = ", "), "\n")
## Top 3 models selected for ensemble: XGBoost, Random Forest, Multinomial
# Retrieve already-computed test set predictions for each model
get_pred_m2 <- function(model_name) {
  if (model_name == "Multinomial")   return(pred_multinom_m2)
  if (model_name == "Perceptron")    return(pred_perc_m2)
  if (model_name == "XGBoost")       return(pred_xgb_m2)
  if (model_name == "Random Forest") return(pred_rf_m2)
}

# Combine top 3 predictions into a data frame
pred_df_m2 <- as.data.frame(
  lapply(top3_names_m2, get_pred_m2)
)
colnames(pred_df_m2) <- top3_names_m2

# Apply majority voting
ensemble_prediction_m2 <- apply(pred_df_m2, 1, function(row) {
  names(sort(table(row), decreasing = TRUE))[1]
})

# Convert to factor with correct levels
ensemble_prediction_m2 <- factor(ensemble_prediction_m2,
                                 levels = levels(test_data_RQ2$attack_severity_label))

# Calculate metrics
metrics_ensemble_m2 <- calculate_metrics(ensemble_prediction_m2,
                                         test_data_RQ2$attack_severity_label)
cat("Ensemble Metrics (Model 2):\n")
## Ensemble Metrics (Model 2):
print(metrics_ensemble_m2)
## $Accuracy
## [1] 0.4497229
## 
## $Precision
## [1] 0.4568732
## 
## $Recall
## [1] 0.4172265
## 
## $F1_Score
## [1] 0.3651889
# Ensemble confusion matrix (Model 2)
cm_ensemble_m2 <- confusionMatrix(ensemble_prediction_m2,
                                  test_data_RQ2$attack_severity_label)

cat("Confusion Matrix - Ensemble (Model 2):\n")
## Confusion Matrix - Ensemble (Model 2):
print(cm_ensemble_m2$table)
##           Reference
## Prediction  Low  Med High
##       Low  1515  989  906
##       Med    30   75   56
##       High  382  417  682
cat("\nPer-class breakdown:\n")
## 
## Per-class breakdown:
print(cm_ensemble_m2$byClass[, c("Precision", "Recall", "F1")])
##             Precision     Recall         F1
## Class: Low  0.4442815 0.78619616 0.56773468
## Class: Med  0.4658385 0.05064146 0.09135201
## Class: High 0.4604997 0.41484185 0.43648000

Evaluations metrics bar plot

# Ensemble Model Evaluation Metrics Bar Plot (Model 2)
if (!exists("metrics_ensemble_m2")) {
  stop("Run the ensemble chunk first before running this plot.")
}

metrics_df_m2 <- data.frame(
  Metric = c("Accuracy", "Precision", "Recall", "F1-Score"),
  Value  = c(metrics_ensemble_m2$Accuracy,
             metrics_ensemble_m2$Precision,
             metrics_ensemble_m2$Recall,
             metrics_ensemble_m2$F1_Score)
)

ggplot(metrics_df_m2, aes(x = Metric, y = Value, fill = Metric)) +
  geom_bar(stat = "identity", width = 0.6) +
  geom_text(aes(label = round(Value, 3)), vjust = -0.5, size = 5) +
  ylim(0, 1) +
  labs(title = "Ensemble Model Evaluation Metrics (RQ2 Model 2)",
       x = "Metric",
       y = "Value",
       fill = "Metric") +
  theme_minimal() +
  theme(
    plot.title = element_text(hjust = 0.5, size = 16, face = "bold"),
    axis.title = element_text(size = 14),
    axis.text = element_text(size = 12)
  ) +
  scale_fill_brewer(palette = "Set2")

Comparing ensemble model 1 and ensemble model 2

# --- Comparing Ensemble Model 1 and Ensemble Model 2 ---
library(ggplot2)
library(reshape2)

comparison_df <- data.frame(
  Metric    = c("Accuracy", "Precision", "Recall", "F1 Score"),
  Ensemble1 = c(metrics_ensemble$Accuracy,
                metrics_ensemble$Precision,
                metrics_ensemble$Recall,
                metrics_ensemble$F1_Score),
  Ensemble2 = c(metrics_ensemble_m2$Accuracy,
                metrics_ensemble_m2$Precision,
                metrics_ensemble_m2$Recall,
                metrics_ensemble_m2$F1_Score)
)

# Melt for plotting
comparison_melt <- melt(comparison_df, 
                        id.vars      = "Metric", 
                        variable.name = "Ensemble", 
                        value.name   = "Value")

# Plot
ggplot(comparison_melt, aes(x = Metric, y = Value, fill = Ensemble)) +
  geom_bar(stat = "identity", position = position_dodge()) +
  geom_text(aes(label = round(Value, 3)),
            position = position_dodge(width = 0.9),
            vjust = -0.3, size = 3.5) +
  ylim(0, 1.1) +
  labs(title = "Comparison of Ensemble Model 1 vs Model 2 (RQ2)",
       x = "Performance Metric",
       y = "Value",
       fill = "Ensemble Model") +
  theme_minimal() +
  theme(
    plot.title  = element_text(hjust = 0.5, size = 14, face = "bold"),
    axis.title  = element_text(size = 12),
    axis.text   = element_text(size = 10),
    legend.title = element_text(size = 12),
    legend.text  = element_text(size = 10)
  ) +
  scale_fill_manual(values = c("Ensemble1" = "steelblue", 
                               "Ensemble2" = "darkorange"))

SAVE the code and figures

# ── EXPORT RESULTS TO CSV — RQ2 Model 2 ───────────────────────────────────────

output_dir_m2 <- "Data/Outputs/RQ2Model2"
if (!dir.exists(output_dir_m2)) dir.create(output_dir_m2, recursive = TRUE)

results_table_m2 <- data.frame(
  Metric        = c("Accuracy", "Precision", "Recall", "F1 Score"),
  Multinomial   = c(metrics_multinom_m2$Accuracy, metrics_multinom_m2$Precision,
                    metrics_multinom_m2$Recall,    metrics_multinom_m2$F1_Score),
  Perceptron    = c(metrics_perc_m2$Accuracy,     metrics_perc_m2$Precision,
                    metrics_perc_m2$Recall,        metrics_perc_m2$F1_Score),
  XGBoost       = c(metrics_xgb_m2$Accuracy,      metrics_xgb_m2$Precision,
                    metrics_xgb_m2$Recall,         metrics_xgb_m2$F1_Score),
  Random_Forest = c(metrics_rf_m2$Accuracy,       metrics_rf_m2$Precision,
                    metrics_rf_m2$Recall,          metrics_rf_m2$F1_Score),
  Ensemble      = c(metrics_ensemble_m2$Accuracy,  metrics_ensemble_m2$Precision,
                    metrics_ensemble_m2$Recall,     metrics_ensemble_m2$F1_Score)
)

results_table_m2[, 2:6] <- round(results_table_m2[, 2:6], 4)

write.csv(results_table_m2, file.path(output_dir_m2, "RQ2_M2_Results.csv"),
          row.names = FALSE)

cat("Files saved:\n")
## Files saved:
cat(paste0("- ", output_dir_m2, "/RQ2_M2_Results.csv\n"))
## - Data/Outputs/RQ2Model2/RQ2_M2_Results.csv
# ── EXPORT — Confusion Matrix Plots — RQ2 Model 2 ─────────────────────────────

# Recompute test set predictions to ensure correct length
pred_multinom_m2_test <- predict(multinom_model_rq2, newdata = test_data_RQ2)
pred_perc_m2_test     <- predict(perc_model_rq2,    newdata = test_data_RQ2)
pred_rf_m2_test       <- predict(rf_model_rq2,      newdata = test_data_RQ2)

cm_models_m2 <- list(
  "Multinomial"   = list(pred = pred_multinom_m2_test, 
                         true = test_data_RQ2$attack_severity_label),
  "Perceptron"    = list(pred = pred_perc_m2_test,     
                         true = test_data_RQ2$attack_severity_label),
  "XGBoost"       = list(pred = pred_xgb_m2,           
                         true = test_data_RQ2$attack_severity_label),
  "RandomForest"  = list(pred = pred_rf_m2_test,       
                         true = test_data_RQ2$attack_severity_label),
  "Ensemble"      = list(pred = ensemble_prediction_m2, 
                         true = test_data_RQ2$attack_severity_label)
)

for (model_name in names(cm_models_m2)) {
  cm <- confusionMatrix(cm_models_m2[[model_name]]$pred,
                        cm_models_m2[[model_name]]$true)
  cm_df <- as.data.frame(cm$table)
  p_cm <- ggplot(cm_df, aes(x = Reference, y = Prediction, fill = Freq)) +
    geom_tile(color = "white") +
    scale_fill_gradient(low = "white", high = "steelblue") +
    geom_text(aes(label = Freq), color = "black", size = 5) +
    labs(title = paste("Confusion Matrix -", model_name, "(RQ2 Model 2)"),
         x = "Actual", y = "Predicted", fill = "Frequency") +
    theme_minimal() +
    theme(plot.title = element_text(hjust = 0.5, size = 14, face = "bold"))
  ggsave(file.path(output_dir_m2, paste0("RQ2_M2_CM_", model_name, ".png")),
         plot = p_cm, width = 6, height = 5, dpi = 300)
}

cat("All confusion matrix plots saved to", output_dir_m2, "\n")
## All confusion matrix plots saved to Data/Outputs/RQ2Model2
# ── EXPORT — Comparison and Ensemble Metrics Plots — RQ2 Model 2 ──────────────

# Ensemble metrics bar plot
p_ensemble_m2 <- ggplot(metrics_df_m2, aes(x = Metric, y = Value, fill = Metric)) +
  geom_bar(stat = "identity", width = 0.6) +
  geom_text(aes(label = round(Value, 3)), vjust = -0.5, size = 5) +
  ylim(0, 1) +
  labs(title = "Ensemble Model Evaluation Metrics (RQ2 Model 2)",
       x = "Metric", y = "Value", fill = "Metric") +
  theme_minimal() +
  theme(plot.title = element_text(hjust = 0.5, size = 16, face = "bold"),
        axis.title = element_text(size = 14),
        axis.text  = element_text(size = 12)) +
  scale_fill_brewer(palette = "Set2")
ggsave(file.path(output_dir_m2, "RQ2_M2_Ensemble_Metrics.png"),
       plot = p_ensemble_m2, width = 7, height = 5, dpi = 300)

# Ensemble comparison plot
p_comparison <- ggplot(comparison_melt, aes(x = Metric, y = Value, fill = Ensemble)) +
  geom_bar(stat = "identity", position = position_dodge()) +
  geom_text(aes(label = round(Value, 3)),
            position = position_dodge(width = 0.9), vjust = -0.3, size = 3.5) +
  ylim(0, 1.1) +
  labs(title = "Comparison of Ensemble Model 1 vs Model 2 (RQ2)",
       x = "Performance Metric", y = "Value", fill = "Ensemble Model") +
  theme_minimal() +
  theme(plot.title  = element_text(hjust = 0.5, size = 14, face = "bold"),
        axis.title  = element_text(size = 12),
        axis.text   = element_text(size = 10)) +
  scale_fill_manual(values = c("Ensemble1" = "steelblue",
                               "Ensemble2" = "darkorange"))
ggsave(file.path(output_dir_m2, "RQ2_M2_Ensemble_Comparison.png"),
       plot = p_comparison, width = 8, height = 5, dpi = 300)

# Perceptron ROC curves
if (exists("roc_perc_list")) {
  p_roc_perc <- pROC::ggroc(roc_perc_list, aes = c("color")) +
    labs(title = "ROC Curves - Perceptron (RQ2 Model 2)",
         x = "False Positive Rate", y = "True Positive Rate", color = "Class") +
    theme_minimal() +
    theme(plot.title = element_text(hjust = 0.5, size = 14, face = "bold"))
  ggsave(file.path(output_dir_m2, "RQ2_M2_ROC_Perceptron.png"),
         plot = p_roc_perc, width = 7, height = 5, dpi = 300)
  cat("Perceptron ROC saved\n")
} else {
  cat("Perceptron ROC list not found — run the ROC chunk first\n")
}
## Perceptron ROC saved
cat("All plots saved to", output_dir_m2, "\n")
## All plots saved to Data/Outputs/RQ2Model2