Show code
# tidyverse loads the tools used to import, clean, reshape, summarize, and graph the data.
# knitr is used to display the final tables in a readable format.
library(tidyverse)
library(knitr)2026-11-08
I used a laptop-price dataset because I work with computer equipment. My question is: How can I use Tidyverse to organize laptop specifications and compare prices?
This is a programming example, not a purchasing recommendation. I use 16 GB of RAM and 512 GB of storage as an example standard so I can demonstrate filtering and comparison. It is not an official employer requirement.
# tidyverse loads the tools used to import, clean, reshape, summarize, and graph the data.
# knitr is used to display the final tables in a readable format.
library(tidyverse)
library(knitr)These are the main functions I used:
read_csv() to import the CSV file.select() to keep the columns needed for the example.mutate() to clean the brand names and create the example group.count(), group_by(), and summarise() to calculate totals and median prices.filter() to keep laptops that meet the example standard.ggplot() to make the charts.# Read the laptop data stored in the data folder.
# show_col_types = FALSE keeps the rendered report from printing a long import message.
laptops_raw <- read_csv("data/laptops.csv", show_col_types = FALSE)
# Display the first six rows, but only use columns that fit on the page.
laptops_raw |>
select(brand, model, device_category, price, ram_gb, storage_gb) |>
slice_head(n = 6) |>
kable(
col.names = c("Brand", "Model", "Category", "Price (INR)", "RAM (GB)", "Storage (GB)"),
caption = "First six laptop records"
)| Brand | Model | Category | Price (INR) | RAM (GB) | Storage (GB) |
|---|---|---|---|---|---|
| lenovo | Lenovo Legion Pro 5 16IAX10 83F3007CIN Gaming | Gaming | 189990 | 32 | 1024 |
| lenovo | Lenovo IdeaPad Slim 3 83K100HSIN | Thin & Light | 62350 | 16 | 1024 |
| asus | Asus Vivobook Go 15 E1504FA-BQ2343WS | Thin & Light | 34990 | 16 | 512 |
| lenovo | Lenovo V14 G3 82TS00JGUS | General | 56990 | 16 | 512 |
| dell | Dell DC 14250 ODC1425000401RINS1 | General | 69990 | 16 | 512 |
| dell | Dell 14 Inspiron 5440 (100UU / 16GB / 512GB / Windows) | General | 37800 | 16 | 512 |
The file has 992 laptop records and 27 columns. The prices are in Indian rupees (INR), not U.S. dollars.
# Put the basic checks into one small table.
# These checks confirm the size of the file and show whether any values are missing.
data_check <- tibble(
check = c("Rows", "Columns", "Missing cells", "Duplicate rows"),
result = c(
nrow(laptops_raw),
ncol(laptops_raw),
sum(is.na(laptops_raw)),
sum(duplicated(laptops_raw))
)
)
data_check |>
kable(
col.names = c("Check", "Result"),
caption = "Basic data-quality checks"
)| Check | Result |
|---|---|
| Rows | 992 |
| Columns | 27 |
| Missing cells | 2289 |
| Duplicate rows | 0 |
I found no duplicate rows. Most of the missing values are in detailed CPU and GPU columns. The six fields used in my main comparison do not have missing values, so I kept all 992 rows.
# First, count the missing cells in each column.
missing_by_column <- laptops_raw |>
summarise(across(everything(), ~ sum(is.na(.x)))) |>
# Turn the wide result into two columns: column name and missing count.
pivot_longer(
cols = everything(),
names_to = "column",
values_to = "missing_values"
) |>
# Keep only the columns that have at least one missing value.
filter(missing_values > 0) |>
arrange(desc(missing_values))
missing_by_column |>
kable(
col.names = c("Column", "Missing values"),
caption = "Columns that contain missing values"
)| Column | Missing values |
|---|---|
| cpu_lp_e_cores | 826 |
| gpu_vram_gb | 700 |
| cpu_e_cores | 311 |
| cpu_p_cores | 289 |
| cpu_suffix | 131 |
| gpu_brand | 8 |
| gpu_series | 8 |
| gpu_model | 8 |
| gpu_type | 8 |
# Keep only the six columns used later in the report.
laptops <- laptops_raw |>
select(
brand, model, device_category, price, ram_gb, storage_gb
) |>
mutate(
# Change brand names to title case and correct the HP label.
brand = str_to_title(brand),
brand = if_else(brand == "Hp", "HP", brand),
# Mark the laptops that meet the RAM and storage example.
replacement_example = if_else(
ram_gb >= 16 & storage_gb >= 512,
"Meets 16 GB + 512 GB example",
"Does not meet example"
)
)if_else() checks if each laptop has at least 16 GB of RAM and 512 GB of storage.
# Count both groups and calculate the percentage of all laptops in each group.
standard_summary <- laptops |>
count(replacement_example, name = "laptops") |>
mutate(percent = scales::percent(laptops / sum(laptops), accuracy = 0.1))
standard_summary |>
kable(
col.names = c("Example group", "Laptops", "Percent"),
caption = "Laptop records by the example replacement standard"
)| Example group | Laptops | Percent |
|---|---|---|
| Does not meet example | 223 | 22.5% |
| Meets 16 GB + 512 GB example | 769 | 77.5% |
In this file, 769 of the 992 laptops (77.5%) meet the example. That does not mean all 769 would be a good purchase. I am only checking RAM and storage here.
# Group the records by laptop category.
# The median is used because a few very expensive laptops can pull the average upward.
category_summary <- laptops |>
group_by(device_category) |>
summarise(
laptops = n(),
median_price_inr = median(price),
.groups = "drop"
) |>
arrange(desc(laptops))
category_summary |>
mutate(median_price_inr = scales::comma(median_price_inr)) |>
kable(
col.names = c("Laptop category", "Number of laptops", "Median price (INR)"),
caption = "Laptop count and median price by category"
)| Laptop category | Number of laptops | Median price (INR) |
|---|---|---|
| General | 390 | 51,945 |
| Gaming | 258 | 90,094 |
| Thin & Light | 207 | 59,990 |
| Business | 78 | 63,494 |
| Convertible | 59 | 107,900 |
General laptops are the largest group with 390 records. Gaming laptops have a higher median price than the general, thin-and-light, and business groups in this file.
# Make a dot chart from the same category_summary table shown above.
# The line helps connect each category to its value, and the exact median is printed.
ggplot(
category_summary,
aes(x = median_price_inr, y = reorder(device_category, median_price_inr))
) +
geom_point(color = "#009E73", size = 4) +
geom_text(
aes(label = scales::comma(median_price_inr)),
hjust = -0.08,
size = 3.5
) +
scale_x_continuous(
labels = scales::label_number(big.mark = ","),
expand = expansion(mult = c(0, 0.18))
) +
labs(
title = "Median laptop price by category",
subtitle = "Prices are listed in Indian rupees",
x = "Median price (INR)",
y = NULL
) +
theme_minimal(base_size = 11) +
theme(panel.grid.major.y = element_blank())I used mutate() to make the comparison groups, filter() to apply the example standard, and summarise() to calculate median prices. My main takeaway is that Tidyverse made it easier to reduce a wide CSV file to the columns and summaries I needed.
The data covers laptop listings in India in 2025, and the prices may have changed. It does not represent the current U.S. market. My example standard only checks RAM and storage, so it should not be used as a final purchasing rule.
I used ChatGPT and VS Code to help draft, troubleshoot, and organize parts of the code and documentation.