Introduction

For my second dataset, I selected Pokémon Base Set card grading population data. Pokémon cards have become popular among collectors, and professionally graded cards can vary greatly in rarity. In this project, I explored how often cards received a Grade 10 compared with lower grades and whether different editions had different grading patterns.

Data Source

The dataset came from the PriceCharting Pokémon Base Set population report.

The dataset contained card names, grading population counts for Grades 6 through 10, and total graded populations. I saved the original data in a wide-format CSV file named pokemon_grading_wide.csv.

Research Question

Which Pokémon Base Set cards had the highest and lowest Grade 10 rates, and how did grading patterns differ between 1st Edition and other card variants?

Data Tidying Approach

The original dataset was in wide format because Grades 6 through 10 were stored as separate columns.

I used pivot_longer() from the tidyr package to convert these grade columns into two variables: Grade and Population.

I also used dplyr to remove empty rows, clean card names, convert grading counts into numeric values, and categorize cards by edition based on their names.

The original CSV remained unchanged throughout the analysis.

Analysis Approach

I calculated the Grade 10 rate for each card by dividing the number of Grade 10 copies by the reported total graded population.

I compared Grade 10 rates across cards and editions. I also investigated whether cards with larger grading populations tended to have higher or lower Grade 10 rates.

I created five visualizations: a bar chart of the highest Grade 10 rates, a boxplot comparing editions, a scatterplot comparing grading population with Grade 10 rates, a stacked bar chart showing the distribution of Grades 6 through 10, and a bar chart of the lowest Grade 10 rates.

For the main visualizations, I focused on cards with at least 100 graded copies to reduce the influence of very small grading populations.

Data Cleaning Challenges

One challenge was that the original dataset contained 931 rows, including 465 completely empty rows. After removing these rows, 466 records remained.

Some grading counts contained commas, while others used dashes instead of numbers. I converted the numeric values and treated dashes as missing data.

Another challenge was identifying different card editions. I categorized cards as 1st Edition, Shadowless, 1999–2000, or Other based on their names. The Other category included several different variants.

The dataset also contained some non-card products, which I identified as a limitation when interpreting the results.

Outcome

The analysis showed that Grade 10 rates differed across Pokémon Base Set cards and editions. Some cards with large grading populations had surprisingly low Grade 10 rates.

The results also showed that grading population and Grade 10 rate had a negative relationship in the analyzed dataset.

Overall, this project demonstrated why comparing percentages provided a more useful understanding of grading outcomes than looking at Grade 10 counts alone.

Data Preparation and Exploration

Load Required Packages

library(tidyverse)
library(knitr)

Import the Original Dataset

pokemon_raw <- read_csv(
  "data/pokemon_grading_wide.csv",
  show_col_types = FALSE
)
## New names:
## • `` -> `...1`
dim(pokemon_raw)
## [1] 931   8
names(pokemon_raw)
## [1] "...1"     "Card"     "Grade 6"  "Grade 7"  "Grade 8"  "Grade 9"  "Grade 10"
## [8] "Total"
head(pokemon_raw, 10) %>%
  kable(caption = "Original Pokémon Grading Dataset")
Original Pokémon Grading Dataset
…1 Card Grade 6 Grade 7 Grade 8 Grade 9 Grade 10 Total
Picture Charizard [1st Edition] #4 953 708 836 779 161 5,980
NA NA NA NA NA NA NA NA
Picture Charizard #4 17,851 16,981 15,932 8,457 490 99,255
NA NA NA NA NA NA NA NA
Picture Machamp [1st Edition] #8 1,801 1,410 1,332 582 50 9,287
NA NA NA NA NA NA NA NA
Picture Mewtwo #10 46 78 104 74 9 393
NA NA NA NA NA NA NA NA
Picture Blastoise #2 53 40 44 18 3 236
NA NA NA NA NA NA NA NA

Examine the Data Structure

glimpse(pokemon_raw)
## Rows: 931
## Columns: 8
## $ ...1       <chr> "Picture", NA, "Picture", NA, "Picture", NA, "Picture", NA,…
## $ Card       <chr> "Charizard [1st Edition] #4", NA, "Charizard #4", NA, "Mach…
## $ `Grade 6`  <chr> "953", NA, "17,851", NA, "1,801", NA, "46", NA, "53", NA, "…
## $ `Grade 7`  <chr> "708", NA, "16,981", NA, "1,410", NA, "78", NA, "40", NA, "…
## $ `Grade 8`  <chr> "836", NA, "15,932", NA, "1,332", NA, "104", NA, "44", NA, …
## $ `Grade 9`  <chr> "779", NA, "8,457", NA, "582", NA, "74", NA, "18", NA, "929…
## $ `Grade 10` <chr> "161", NA, "490", NA, "50", NA, "9", NA, "3", NA, "96", NA,…
## $ Total      <chr> "5,980", NA, "99,255", NA, "9,287", NA, "393", NA, "236", N…
colSums(is.na(pokemon_raw))
##     ...1     Card  Grade 6  Grade 7  Grade 8  Grade 9 Grade 10    Total 
##      465      465      465      465      465      465      465      465

Cleaning the Pokémon Dataset

The original dataset contained 931 rows, including 465 completely empty rows. I removed these empty rows and the unnecessary picture column.

The grading counts were originally stored as text because some values contained commas. I converted these values into numeric format and treated dashes as missing values.

I also identified card editions using the card names.

pokemon_clean <- pokemon_raw %>%
  filter(!if_all(everything(), is.na)) %>%
  select(-`...1`) %>%
  mutate(
    Card = str_trim(Card),
    across(
      c(`Grade 6`, `Grade 7`, `Grade 8`,
        `Grade 9`, `Grade 10`, Total),
      ~ parse_number(.x, na = c("", "NA", "-"))
    ),
    Edition = case_when(
      str_detect(Card, regex("1st Edition", ignore_case = TRUE)) ~ "1st Edition",
      str_detect(Card, regex("Shadowless", ignore_case = TRUE)) ~ "Shadowless",
      str_detect(Card, regex("1999-2000", ignore_case = TRUE)) ~ "1999-2000",
      TRUE ~ "Other"
    )
  ) %>%
  filter(!is.na(Card), Card != "")

dim(pokemon_clean)
## [1] 466   8
pokemon_clean %>%
  select(Card, Edition, `Grade 10`, Total) %>%
  head(10) %>%
  kable(caption = "Cleaned Pokémon Grading Data")
Cleaned Pokémon Grading Data
Card Edition Grade 10 Total
Charizard [1st Edition] #4 1st Edition 161 5980
Charizard #4 Other 490 99255
Machamp [1st Edition] #8 1st Edition 50 9287
Mewtwo #10 Other 9 393
Blastoise #2 Other 3 236
Charizard [Shadowless] #4 Shadowless 96 14861
Pikachu #58 Other 455 5697
Venusaur #15 Other 3 12
Gyarados #6 Other 12 345
Pikachu [1st Edition] #58 1st Edition 582 4746

Calculating Grade 10 Rates

I calculated the Grade 10 rate by dividing the number of Grade 10 copies by the total graded population for each card.

A higher Grade 10 rate indicates that a larger percentage of the reported graded copies received the highest grade. A lower rate indicates that Grade 10 copies are less common among the graded population.

pokemon_rates <- pokemon_clean %>%
  filter(
    !is.na(Total),
    Total > 0,
    !is.na(`Grade 10`)
  ) %>%
  mutate(
    Grade10Rate = (`Grade 10` / Total) * 100
  )

pokemon_rates %>%
  select(Card, Edition, `Grade 10`, Total, Grade10Rate) %>%
  arrange(desc(Grade10Rate)) %>%
  head(10) %>%
  kable(
    digits = 2,
    caption = "Highest Grade 10 Rates"
  )
Highest Grade 10 Rates
Card Edition Grade 10 Total Grade10Rate
Water Energy [1999-2000] #102 1999-2000 169 285 59.30
Wartortle [Trainer Deck B] #42 Other 54 92 58.70
Starmie [Trainer Deck B] #64 Other 71 124 57.26
Gust of Wind [1999-2000] #93 1999-2000 100 181 55.25
Professor Oak [1999-2000] #88 1999-2000 67 123 54.47
Fighting Energy [1999-2000] #97 1999-2000 138 259 53.28
Fire Energy [1999-2000] #98 1999-2000 180 340 52.94
Energy Removal [1999-2000] #92 1999-2000 101 192 52.60
Grass Energy [1999-2000] #99 1999-2000 124 240 51.67
Squirtle [Trainer Deck B] #63 Other 96 190 50.53

Data Visualization

Visualization 1: Highest Grade 10 Rates

This chart shows the ten cards with the highest percentage of Grade 10 copies among cards with at least 100 total graded copies. I used this minimum population to reduce the influence of very small grading samples.

pokemon_top10 <- pokemon_rates %>%
  filter(Total >= 100) %>%
  arrange(desc(Grade10Rate)) %>%
  slice_head(n = 10)

ggplot(
  pokemon_top10,
  aes(
    x = reorder(Card, Grade10Rate),
    y = Grade10Rate,
    fill = Edition
  )
) +
  geom_col() +
  geom_text(
    aes(label = paste0(round(Grade10Rate, 1), "%")),
    hjust = -0.1,
    size = 3.5
  ) +
  coord_flip() +
  scale_y_continuous(
    limits = c(0, 70)
  ) +
  labs(
    title = "Pokémon Cards with the Highest Grade 10 Rates",
    subtitle = "Cards with at least 100 graded copies",
    x = "Pokémon Card",
    y = "Grade 10 Rate (%)",
    fill = "Edition"
  ) +
  theme_minimal()

This visualization compares Grade 10 rates rather than the total number of Grade 10 copies. It helps distinguish cards that frequently receive the highest grade from cards that simply have large grading populations.

The results are limited to cards with at least 100 graded copies. This threshold is an analysis choice rather than an official grading standard.

Visualization 2: Grade 10 Rates by Edition

This visualization compares the distribution of Grade 10 rates across different Pokémon Base Set variants.

pokemon_rates %>%
  filter(Total >= 100) %>%
  ggplot(
    aes(
      x = Edition,
      y = Grade10Rate,
      fill = Edition
    )
  ) +
  geom_boxplot(alpha = 0.7) +
  labs(
    title = "Grade 10 Rates Across Pokémon Card Editions",
    subtitle = "Cards with at least 100 graded copies",
    x = "Card Edition",
    y = "Grade 10 Rate (%)"
  ) +
  theme_minimal() +
  theme(
    legend.position = "none"
  )

The boxplots show how Grade 10 rates vary within each edition category. The median represents the middle rate, while the boxes show the spread of the middle 50% of card rates.

This helps determine whether Grade 10 results differ across editions and whether some editions have more variation than others.

Visualization 3: Total Graded Population vs. Grade 10 Rate

This visualization explores the relationship between the total number of graded Pokémon cards and the percentage receiving Grade 10. It helps determine whether cards with larger grading populations also tend to have higher Grade 10 rates.

pokemon_rates %>%
  filter(Total >= 100) %>%
  mutate(LogTotal = log10(Total)) %>%
  ggplot(
    aes(
      x = LogTotal,
      y = Grade10Rate,
      color = Edition
    )
  ) +
  geom_point(alpha = 0.7, size = 2.5) +
  geom_smooth(
    method = "lm",
    se = FALSE,
    color = "black",
    linetype = "dashed"
  ) +
  scale_x_continuous(
    breaks = 2:5,
    labels = scales::comma(10^(2:5))
  ) +
  labs(
    title = "Does Grading Population Affect Grade 10 Rates?",
    subtitle = "Pokémon Base Set cards with at least 100 graded copies",
    x = "Total Graded Population (Log Scale)",
    y = "Grade 10 Rate (%)",
    color = "Edition"
  ) +
  theme_minimal()
## `geom_smooth()` using formula = 'y ~ x'

Each point represents a Pokémon card or variant. The horizontal axis shows the total graded population, while the vertical axis shows the percentage receiving Grade 10. The dashed line summarizes the overall relationship between grading population and Grade 10 rate.

Visualization 4: Grade Distribution by Edition

This visualization compares the percentage of grading results from Grade 6 through Grade 10 across different Pokémon card editions.

pokemon_tidy <- pokemon_clean %>%
  pivot_longer(
    cols = c(`Grade 6`, `Grade 7`, `Grade 8`,
             `Grade 9`, `Grade 10`),
    names_to = "Grade",
    values_to = "Population"
  ) %>%
  mutate(
    Grade = factor(
      Grade,
      levels = paste("Grade", 6:10)
    )
  )

pokemon_tidy %>%
  select(Card, Edition, Grade, Population) %>%
  head(10) %>%
  kable(caption = "Tidy Pokémon Grading Dataset")
Tidy Pokémon Grading Dataset
Card Edition Grade Population
Charizard [1st Edition] #4 1st Edition Grade 6 953
Charizard [1st Edition] #4 1st Edition Grade 7 708
Charizard [1st Edition] #4 1st Edition Grade 8 836
Charizard [1st Edition] #4 1st Edition Grade 9 779
Charizard [1st Edition] #4 1st Edition Grade 10 161
Charizard #4 Other Grade 6 17851
Charizard #4 Other Grade 7 16981
Charizard #4 Other Grade 8 15932
Charizard #4 Other Grade 9 8457
Charizard #4 Other Grade 10 490
pokemon_tidy %>%
  filter(
    !is.na(Population),
    !is.na(Total),
    Total >= 100
  ) %>%
  group_by(Edition, Grade) %>%
  summarise(
    TotalPopulation = sum(Population),
    .groups = "drop"
  ) %>%
  group_by(Edition) %>%
  mutate(
    Percentage = TotalPopulation /
      sum(TotalPopulation) * 100
  ) %>%
  ggplot(
    aes(
      x = Edition,
      y = Percentage,
      fill = Grade
    )
  ) +
  geom_col(
    position = "stack",
    width = 0.7
  ) +
  scale_y_continuous(
    labels = scales::label_percent(scale = 1)
  ) +
  labs(
    title = "Pokémon Grade Distribution by Edition",
    subtitle = "Distribution of Grades 6–10 for cards with at least 100 graded copies",
    x = "Card Edition",
    y = "Percentage of Grades 6–10",
    fill = "Grade"
  ) +
  theme_minimal()

This stacked bar chart shows how Grades 6 through 10 are distributed across different card editions. It helps identify whether some editions have a larger share of higher grades than others. Percentages are calculated using only the displayed Grades 6–10, not the entire reported grading population.

Visualization 5: Lowest Grade 10 Rates

This visualization shows the ten Pokémon cards with the lowest Grade 10 rates among cards with at least 100 graded copies.

pokemon_bottom10 <- pokemon_rates %>%
  filter(Total >= 100) %>%
  arrange(Grade10Rate) %>%
  slice_head(n = 10)

ggplot(
  pokemon_bottom10,
  aes(
    x = reorder(Card, Grade10Rate),
    y = Grade10Rate,
    fill = Edition
  )
) +
  geom_col() +
  geom_text(
    aes(label = paste0(round(Grade10Rate, 2), "%")),
    hjust = -0.1,
    size = 3.5
  ) +
  coord_flip() +
  scale_y_continuous(
    limits = c(0, 1.4)
  ) +
  labs(
    title = "Pokémon Cards with the Lowest Grade 10 Rates",
    subtitle = "Cards with at least 100 graded copies",
    x = "Pokémon Card",
    y = "Grade 10 Rate (%)",
    fill = "Edition"
  ) +
  theme_minimal()

This chart highlights cards where Grade 10 copies were especially uncommon compared with their total graded populations. It provides a useful comparison with the highest Grade 10 rates shown in Visualization 1.

Grade 10 Rate Summary by Edition

pokemon_rates %>%
  filter(Total >= 100) %>%
  group_by(Edition) %>%
  summarise(
    Cards = n(),
    AverageGrade10Rate = round(mean(Grade10Rate), 2),
    MedianGrade10Rate = round(median(Grade10Rate), 2),
    .groups = "drop"
  ) %>%
  kable(
    caption = "Grade 10 Rate Summary by Edition"
  )
Grade 10 Rate Summary by Edition
Edition Cards AverageGrade10Rate MedianGrade10Rate
1999-2000 39 36.96 34.99
1st Edition 103 14.43 14.30
Other 118 16.69 16.03
Shadowless 101 15.98 16.77

Results and Findings

The analysis showed that Grade 10 rates varied considerably across Pokémon Base Set cards and editions.

Among cards with at least 100 graded copies, the 1999–2000 edition had the highest average Grade 10 rate at 37.0%. The Other category averaged 16.7%, Shadowless averaged 16.0%, and 1st Edition had the lowest average at 14.4%.

One interesting finding was that Charizard #4 had 99,255 graded copies, but only 490 received Grade 10. This represented a Grade 10 rate of approximately 0.49%. In comparison, Water Energy [1999-2000] #102 had 169 Grade 10 copies out of 285 graded copies, resulting in a rate of approximately 59.3%.

The analysis also found a moderate negative correlation of -0.499 between the logarithm of total graded population and Grade 10 rate. This suggested that cards with larger grading populations tended to have lower Grade 10 rates in the analyzed dataset.

These findings showed why comparing percentages was more informative than comparing Grade 10 counts alone.

Challenges and Limitations

One challenge was cleaning the original dataset. It contained 931 rows, including 465 completely empty rows. I removed these rows and converted grading counts from text into numeric values.

Some records contained dashes instead of grading counts. I treated these as missing values rather than assuming they represented zero.The dataset also contained some non-card products, such as sealed packs. Records without valid grading totals or Grade 10 counts were excluded from the Grade 10 rate analysis. However, I did not manually verify every remaining record, so some product types may still have affected the comparisons.

Another limitation was that the dataset reported Grades 6 through 10 separately, while the total graded population could include other grades. I used the reported total when calculating Grade 10 rates, but used only Grades 6 through 10 when comparing grade distributions.

I also limited the main visualizations to cards with at least 100 graded copies to reduce the influence of very small grading populations. The edition categories were based on card names, and the Other category included several different variants.

Finally, the correlation showed an association, not a cause-and-effect relationship. The data did not explain why some cards received higher grades than others.

Conclusion

This project helped me understand how data cleaning and percentages can change the way we interpret Pokémon card rarity.

The results showed that having more graded copies did not necessarily mean having a higher Grade 10 rate. Some cards with very large grading populations had surprisingly low Grade 10 percentages.

I also found differences between editions, especially the higher average Grade 10 rate among 1999–2000 variants.

Overall, the project demonstrated the importance of tidying data, comparing proportions, and using visualizations to discover patterns that are not obvious from raw counts alone.