Code
library(tidyverse)
library(gt)
library(gtExtras)
library(scales)
set.seed(2024)library(tidyverse)
library(gt)
library(gtExtras)
library(scales)
set.seed(2024)gt and gtExtra packages demonstrated in the videos?The videos showed me that tables can be used as a form of data visualization when they are designed carefully. Before this module, I mostly thought of tables as plain rows and columns. However, the videos demonstrated that tables can communicate patterns, comparisons, and key findings when they are formatted professionally. I learned that the gt package allows users to build a table step by step from a data frame. Some of the most useful functions include tab_header() for titles and subtitles, cols_label() for clearer column names, fmt_number() and fmt_currency() for formatting values, tab_source_note() for documenting the data source, and tab_footnote() for explaining important details.
What I liked most about gt is that it gives the user control over the table’s appearance. Instead of showing a basic default table, I can customize titles, subtitles, column labels, spanner labels, borders, fonts, alignment, source notes, and footnotes. These features make the final table look more appropriate for a written report, business presentation, or portfolio project. I also liked gtExtras because it adds visual elements directly inside a table. For example, small bar charts can make it easier to compare groups while still keeping the exact numbers available. This combination of numbers and visuals makes the table easier to scan and more useful for decision-making.
Tables complement charts because they serve a different purpose. Charts are useful for showing broad visual patterns, trends, and relationships. For example, a line chart is good for showing changes over time, while a bar chart is good for quickly comparing categories. Tables are stronger when the reader needs exact values, multiple statistics, and detailed labels. A chart can show that one group performs better than another, but a table can show the exact response rate, sample size, average income, average order amount, and other supporting details.
I would prefer to use tables rather than charts when exact values are important, when several metrics need to be compared at the same time, or when the output needs to be included in a written report. Tables are also useful for descriptive statistics, model summaries, and business performance summaries. In a marketing analytics setting, a table can help managers compare customer groups using multiple measures in one compact display. Charts may be better for quick impressions, but tables are better when accuracy, detail, and documentation matter.1
gt helps make tables professional, while gtExtras helps make tables more visual by adding charts inside the table.
gt / gtExtras TableUse the synthetic data introduced this week for the lecture. Pick some variables of your interest from the data and share some descriptive statistics using gt and gtExtra functions together to show numbers and small charts in one table. In this table, incorporate the following at a minimum:
The synthetic dataset used in this report is a direct-mail marketing dataset. Each row represents one prospect in a customer acquisition campaign. The outcome variable is whether the prospect responded to the campaign by buying. I grouped the data by loyalty tier because loyalty tier is a useful customer segmentation variable for marketing decisions.
set.seed(123)
n <- 3000
mail_data <- tibble(
customer_id = paste0("C", str_pad(1:n, 5, pad = "0")),
age = round(rnorm(n, mean = 45, sd = 12)),
income = round(rlnorm(n, meanlog = 10.8, sdlog = 0.6)),
recency_days = round(rexp(n, rate = 1 / 60)),
freq_12mo = rpois(n, lambda = 3),
avg_order_amt = round(rlnorm(n, meanlog = 4.2, sdlog = 0.5), 2),
channel = sample(
c("email", "direct_mail", "digital"),
n,
replace = TRUE,
prob = c(0.5, 0.3, 0.2)
),
region = sample(
c("West", "South", "Midwest", "Northeast"),
n,
replace = TRUE
),
loyalty_tier = sample(
c("Bronze", "Silver", "Gold", "Platinum"),
n,
replace = TRUE,
prob = c(0.4, 0.3, 0.2, 0.1)
),
responded = rbinom(
n,
1,
prob = plogis(
-3 +
0.02 * (age - 45) +
0.3 * log(income / 50000) +
0.1 * freq_12mo -
0.005 * recency_days
)
)
) |>
mutate(
income = if_else(runif(n) < 0.08, NA_real_, income),
avg_order_amt = if_else(runif(n) < 0.05, NA_real_, avg_order_amt),
responded = factor(responded, levels = c(1, 0), labels = c("yes", "no"))
)The table below summarizes campaign performance and customer value indicators by loyalty tier.
summary_by_tier <- mail_data |>
group_by(loyalty_tier) |>
summarise(
prospects = n(),
response_rate = mean(responded == "yes", na.rm = TRUE),
response_visual = response_rate * 100,
avg_age = mean(age, na.rm = TRUE),
avg_income = mean(income, na.rm = TRUE),
avg_order_amt = mean(avg_order_amt, na.rm = TRUE),
avg_frequency = mean(freq_12mo, na.rm = TRUE),
avg_recency_days = mean(recency_days, na.rm = TRUE),
.groups = "drop"
) |>
mutate(
loyalty_tier = factor(
loyalty_tier,
levels = c("Bronze", "Silver", "Gold", "Platinum")
)
) |>
arrange(loyalty_tier)campaign_table <- summary_by_tier |>
gt(rowname_col = "loyalty_tier") |>
tab_header(
title = md("**Direct-Mail Campaign Performance by Loyalty Tier**"),
subtitle = "Response behavior and customer value indicators from a synthetic acquisition campaign"
) |>
tab_stubhead(label = "Loyalty Tier") |>
tab_spanner(
label = "Campaign Response",
columns = c(prospects, response_rate, response_visual)
) |>
tab_spanner(
label = "Customer Profile",
columns = c(avg_age, avg_income, avg_order_amt, avg_frequency, avg_recency_days)
) |>
cols_label(
prospects = "Prospects",
response_rate = "Response Rate",
response_visual = "Response Visual",
avg_age = "Avg. Age",
avg_income = "Avg. Income",
avg_order_amt = "Avg. Order Amount",
avg_frequency = "Avg. Purchases",
avg_recency_days = "Avg. Recency Days"
) |>
fmt_number(
columns = c(avg_age, avg_frequency, avg_recency_days),
decimals = 2
) |>
fmt_percent(
columns = response_rate,
decimals = 2
) |>
fmt_currency(
columns = c(avg_income, avg_order_amt),
currency = "USD",
decimals = 2
) |>
gt_plt_bar_pct(
column = response_visual,
scaled = TRUE,
labels = TRUE,
decimals = 1
) |>
data_color(
columns = response_rate,
palette = "Blues"
) |>
tab_footnote(
footnote = "Response rate is the percentage of prospects who responded yes to the campaign.",
locations = cells_column_labels(columns = response_rate)
) |>
tab_footnote(
footnote = "Income and average order amount contain simulated missing values, so averages are calculated using available cases.",
locations = cells_column_labels(columns = c(avg_income, avg_order_amt))
) |>
tab_source_note(
source_note = "Source: Synthetic direct-mail marketing dataset created for M05 lecture practice."
) |>
cols_width(
prospects ~ px(90),
response_rate ~ px(120),
response_visual ~ px(160),
avg_age ~ px(90),
avg_income ~ px(130),
avg_order_amt ~ px(140),
avg_frequency ~ px(120),
avg_recency_days ~ px(130)
) |>
opt_table_font(font = list(google_font("Roboto"), default_fonts())) |>
opt_align_table_header(align = "left") |>
tab_options(
table.font.size = px(13),
heading.title.font.size = px(18),
heading.subtitle.font.size = px(13),
column_labels.font.weight = "bold",
row.striping.include_table_body = TRUE,
table.border.top.width = px(2),
table.border.bottom.width = px(2)
)
campaign_table| Direct-Mail Campaign Performance by Loyalty Tier | ||||||||
| Response behavior and customer value indicators from a synthetic acquisition campaign | ||||||||
| Loyalty Tier |
Campaign Response
|
Customer Profile
|
||||||
|---|---|---|---|---|---|---|---|---|
| Prospects | Response Rate1 | Response Visual | Avg. Age | Avg. Income2 | Avg. Order Amount2 | Avg. Purchases | Avg. Recency Days | |
| Bronze | 1263 | 4.83% |
4.8%
|
45.03 | $57,882.58 | $76.67 | 2.96 | 61.33 |
| Silver | 870 | 3.79% |
3.8%
|
44.79 | $59,813.92 | $74.08 | 2.91 | 57.55 |
| Gold | 567 | 4.76% |
4.8%
|
45.49 | $55,316.29 | $76.39 | 2.87 | 63.69 |
| Platinum | 300 | 7.00% |
7%
|
46.21 | $61,947.49 | $75.32 | 3.11 | 57.03 |
| 1 Response rate is the percentage of prospects who responded yes to the campaign. | ||||||||
| 2 Income and average order amount contain simulated missing values, so averages are calculated using available cases. | ||||||||
| Source: Synthetic direct-mail marketing dataset created for M05 lecture practice. | ||||||||
This table is worth adding to my MSDM project because it demonstrates my ability to interpret data and communicate meaningful insights through professional data visualizations. It shows that I can take a dataset, organize and summarize the information, and present it in a way that is easy for others to understand. By using the gt and gtExtras packages, I can create publication-quality tables that combine descriptive statistics with visual elements, making complex information more accessible. These skills are valuable because they can be applied to virtually any dataset, allowing me to analyze data and create effective visualizations that help explain patterns, trends, and key findings to different audiences.
In this report, I use a table because the goal is to compare exact customer segment metrics rather than only show a general pattern.↩︎