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(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
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
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
# 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
### 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 - 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
### 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
THE FOLLOWING MODELS ANSWER RESEARCH QUESTION 2-
RQ2: Does the number of records affected is correlated to the severity of the cyber-attack?
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)))
}
# 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
# 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
# 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
# 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 (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
# 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
# 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
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")
}
# 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")
}
# 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 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 (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 ---
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"))
# ── 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