This is the document for New Donor LTV Predict model using Pledge and
CS Center data.
Prepare the data with data preprocessing
library(readxl)
library(dplyr)
library(lubridate)
library(ggplot2)
library(tidymodels)
library(data.table)
library(ranger)
You can also get this file from Report Function in Charitable,
(Report Number : OP32)
call <- read_excel("call.xlsx") #Import data
call_sel <- call %>% select(`Serial Number`, `Task Group` ,`Task Closed Date`) # Selected columns
call_sel <-
call_sel %>%
group_by(`Serial Number`) %>%
mutate(count = n()) %>%
arrange(desc(`Task Closed Date`)) %>%
distinct(`Serial Number`, .keep_all = TRUE)
#Removed duplicated record by serial No.
call_tran <-
call_sel %>%
select(`Serial Number`, `Task Closed Date`, count) %>%
mutate_if(is.character, as.factor)
#Selected columns again and generated new column
call_tran$`Task Closed Date` <- as.numeric(call_tran$`Task Closed Date`)
#Changed column format as numeric
call_tran <- as.data.frame(call_tran)
Call data will be used to extract the number of calls and check the correlation with Pledge data.
Prepare the data by using the latest records and creating a “count”
column containing the total number of call records.
## Rows: 2,019
## Columns: 3
## $ `Serial Number` <chr> "DR002378", "FF001712", "DR001102", "FF002298", "FF…
## $ `Task Closed Date` <dbl> 1704722898, 1704718174, 1704189267, 1704188748, 170…
## $ count <int> 2, 1, 1, 1, 1, 1, 1, 1, 2, 1, 1, 1, 1, 1, 1, 1, 1, …
You can also get this file from Report Function in Charitable,
(Report Number : OP24)
new <- read_excel("new.xlsx") #Import data
new_date <-
new %>%
filter(`Pledge Created` >= as.Date("2023-07-01") & `Pledge Created` <= as.Date("2024-01-31")) %>%
filter(Frequency == 1)
#selected specific period and recurring pledge
new_sel <-
new_date %>%
select(SerialNo, DOB, Gender, SourceCode, Bank, TotalContribution, ChannelType, D0, `Pledge Created`, successful_transactions_count, Campaign)
#Selected columns
new_tran <-
new_sel %>%
mutate(total =TotalContribution) %>%
dplyr::select(- TotalContribution) %>%
mutate_if(is.character,as.factor)
#Changed Y column name and changed column format
new_tran$DOB <- as.numeric(new_tran$DOB)
new_tran$D0 <- as.numeric(new_tran$D0)
new_tran$`Pledge Created` <- as.numeric(new_tran$`Pledge Created`)
#Changed column format as numeric
As you know, the pledge data contains lots of data including personal
information which has to remove
Data preprocessing was performed by selecting the necessary
columns and changing the characteristics of the columns.
For modeling purposes, a factor column containing many values must
be converted to characters.
new_tran <-
new_tran %>%
mutate(cam = ifelse(is.na(Campaign),0,1)) %>%
dplyr::select(-Campaign)
new_tran$cam <- as.factor(new_tran$cam)
new_tran$SerialNo <- as.character(new_tran$SerialNo)
setnames(call_tran, old = "Serial Number", new = "SerialNo")
setnames(call_tran, old = "Task Closed Date", new = "task date")
pled <-
new_tran %>%
left_join(call_tran, by = "SerialNo")
#Merge data.
## Rows: 1,908
## Columns: 13
## $ SerialNo <chr> "IF001128", "TM000243", "FF005984", "FF0…
## $ DOB <dbl> 113875200, 674956800, 790128000, 1046563…
## $ Gender <fct> Male, Female, Male, Female, Female, Male…
## $ SourceCode <fct> ODP, LLF, FIN, FIN, LLF, FIN, FIN, FIN, …
## $ Bank <fct> KFTC, KFTC, NICEPAY, KFTC, KFTC, NICEPAY…
## $ ChannelType <fct> Web, TM, EVENT, EVENT, TM, EVENT, EVENT,…
## $ D0 <dbl> 1688688000, 1688688000, 1688428800, NA, …
## $ `Pledge Created` <dbl> 1688342400, 1688342400, 1688428800, 1688…
## $ successful_transactions_count <fct> 7, 7, 7, 0, 6, 6, 7, 0, 6, 7, 1, 7, 7, 7…
## $ total <dbl> 140000, 70000, 140000, 0, 40000, 60000, …
## $ cam <fct> 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1…
## $ `task date` <dbl> NA, NA, NA, 1690461012, 1693935143, NA, …
## $ count <int> NA, NA, NA, 1, 1, NA, NA, 1, NA, NA, 1, …
Replaced “NA” value, if the column format is numeric then “Na” Value
to “0”.
if the column format is character or factor then “Na” value
is to “emt” .
pled <-
pled %>%
mutate(task = ifelse(is.na(`task date`),0,`task date`)) %>%
mutate(count = ifelse(is.na(count),0,count)) %>%
select(-`task date`)
pled <-
pled %>%
mutate(DOB = ifelse(is.na(DOB),0,DOB)) %>%
mutate(D0 = ifelse(is.na(D0),0,D0)) %>%
mutate(Gender = if_else(is.na(Gender),"emt",Gender)) %>%
mutate(ChannelType = if_else(is.na(ChannelType),"emt",ChannelType)) %>%
mutate_if(is.character,as.factor)
pled$SerialNo <-as.character(pled$SerialNo)
#Serial No. is not used in the model.
| Name | pled |
| Number of rows | 1908 |
| Number of columns | 13 |
| _______________________ | |
| Column type frequency: | |
| character | 1 |
| factor | 6 |
| numeric | 6 |
| ________________________ | |
| Group variables | None |
Variable type: character
| skim_variable | n_missing | complete_rate | min | max | empty | n_unique | whitespace |
|---|---|---|---|---|---|---|---|
| SerialNo | 0 | 1 | 8 | 8 | 0 | 1908 | 0 |
Variable type: factor
| skim_variable | n_missing | complete_rate | ordered | n_unique | top_counts |
|---|---|---|---|---|---|
| Gender | 0 | 1 | FALSE | 3 | Fem: 1152, Mal: 716, emt: 40 |
| SourceCode | 0 | 1 | FALSE | 6 | FIN: 759, LLF: 625, ODP: 470, REA: 36 |
| Bank | 0 | 1 | FALSE | 5 | KFT: 1468, NIC: 422, MAN: 16, KFT: 1 |
| ChannelType | 0 | 1 | FALSE | 10 | DRT: 586, Web: 470, EVE: 381, STR: 377 |
| successful_transactions_count | 0 | 1 | FALSE | 9 | 0: 423, 1: 303, 2: 287, 3: 259 |
| cam | 0 | 1 | FALSE | 2 | 1: 1892, 0: 16 |
Variable type: numeric
| skim_variable | n_missing | complete_rate | mean | sd | p0 | p25 | p50 | p75 | p100 | hist |
|---|---|---|---|---|---|---|---|---|---|---|
| DOB | 0 | 1 | 2.775817e+08 | 625342970.32 | -1210377600 | -266112000 | 252720000 | 885427200 | 1667260800 | ▂▇▅▇▂ |
| D0 | 0 | 1 | 1.321272e+09 | 705377101.91 | 0 | 1689616800 | 1695168000 | 1700784000 | 1705449600 | ▂▁▁▁▇ |
| Pledge Created | 0 | 1 | 1.697445e+09 | 5153009.31 | 1688342400 | 1693180800 | 1697760000 | 1701993600 | 1705536000 | ▆▆▆▇▇ |
| total | 0 | 1 | 3.952966e+04 | 47810.39 | 0 | 10000 | 30000 | 60000 | 800000 | ▇▁▁▁▁ |
| count | 0 | 1 | 2.500000e-01 | 0.57 | 0 | 0 | 0 | 0 | 6 | ▇▁▁▁▁ |
| task | 0 | 1 | 3.480599e+08 | 685762490.13 | 0 | 0 | 0 | 0 | 1704722898 | ▇▁▁▁▂ |
Building a predictive model
set.seed(123) #Random number settings
split <- initial_split(pled, prop = 0.7) #7:3
train<- training(split)
test <- testing(split)
Preprocessing for modeling, use “receipe” function in “tidymodels”
rf_recipe_1 <-
recipe(total ~ ., data = train) %>%
step_center(all_numeric_predictors()) %>% #Centered from the mean
step_scale(all_numeric_predictors()) %>% #Scaling by sd
step_rm(SerialNo) %>% #Excluding serial number
step_zv(all_numeric_predictors()) %>% #Exclude variables with zero variance (variance is 0 and not predicted)
step_normalize(all_numeric_predictors()) #normalization
mty <- rf_recipe_1 %>%
prep() %>%
bake(new_data = NULL)
#mty value have to follow total independent value contains factor column as well
.
rf_model <-
rand_forest(mtry = 3) %>%
set_engine("ranger") %>%
set_mode("regression")
pre_train <- rf_recipe_1 %>% prep() %>% bake(new_data = train)
pre_test <- rf_recipe_1 %>% prep() %>% bake(new_data = test)
rf_fit <- rf_model %>% fit(total ~ ., data = pre_train)
rf_pred <- rf_fit %>% predict(new_data = pre_test)
com_data <- data.frame(truth = pre_test$total, estimate = rf_pred$.pred)
com_data_tbl <- as_tibble(com_data)
r_squared <- 1 - sum((com_data_tbl$truth - com_data_tbl$estimate)^2) / sum((com_data_tbl$truth - mean(com_data_tbl$truth))^2)
r_squared
## [1] 0.5703958