Laptop Prices with Tidyverse

Andre Thomson

2026-11-08

Purpose

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.

Packages

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)

These are the main functions I used:

Import the data

Show code
# 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"
  )
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.

Check the data

Show code
# 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"
  )
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.

Show code
# 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"
  )
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

Clean and organize the columns

Show code
# 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.

Summarize the example standard

Show code
# 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"
  )
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.

Compare laptop categories

Show code
# 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 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.

Show code
# 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())
Horizontal dot chart of median laptop price in Indian rupees by category. Convertible is highest, followed by gaming, business, thin and light, and general.
Figure 1

Summary

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.

Limitations

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.

Sources

AI use

I used ChatGPT and VS Code to help draft, troubleshoot, and organize parts of the code and documentation.