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.
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.
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?
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.
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.
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.
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.
library(tidyverse)
library(knitr)
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")
| …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 |
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
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")
| 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 |
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"
)
| 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 |
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.
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.
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.
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")
| 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.
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.
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"
)
| 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 |
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.
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.
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.