Game Balance & Roster Complexity
Evaluating balance across 1,025 species with 18 elemental types, dual combinations, and 6 combat attributes requires multidimensional filtering and statistical outlier detection.
The BI Analytical Solution
Built an automated ETL script extracting JSON attributes from PokéAPI into Google Sheets, feeding a Looker Studio portal with dynamic stat radar metrics, type matrices, and generation slicing.
Competitive Meta & Design Impact
Enables game balance designers and competitive players to uncover stat power-creep patterns, evaluate type resistance distributions, and optimize roster builds.
Live Interactive BI Report
Interactive Navigation Guide: Filter by generation, elemental typing, or Base Stat Total range using top controls. Search for any specific species by name or Pokédex number to view its full 6-dimensional stat breakdown and sprite render.
Game Balance & Meta Stat Findings
Statistical evaluation of the complete 1,025 species roster reveals four key insights into franchise game balance evolution:
1. Multi-Generational Stat Creep
Average non-legendary Base Stat Total increased from 407 in Generation 1 (Kanto) to 486 in Generation 9 (Paldea), reflecting a systematic upward progression in offensive baseline viability.
2. Steel & Fairy Defensive Dominance
Steel-type dual pairings boast the highest average defensive ratings across all 18 types (avg Defense 98.4), with Steel/Fairy remaining the most statistically resilient defensive pairing in the meta.
3. Speed Tier Polarization
Speed distributions demonstrate bimodal polarization in modern generations: species are heavily clustered either below 50 Speed (Trick Room archetypes) or above 110 Speed (fast sweepers).
4. Extreme Min-Maxing in Modern Meta
Recent generations exhibit lower stat variance within single attributes, favoring hyper-specialized combat roles ('glass cannons' or 'pure physical walls') over balanced generalist spreads.
Competitive Gaming Analytics & Objectives
The Pokémon franchise spans 9 generations, evolving from 151 original creatures into a complex competitive ecosystem of 1,025 distinct species. Each Pokémon is defined across 6 fundamental combat stats (HP, Attack, Defense, Special Attack, Special Defense, Speed) and can possess single or dual elemental typing.
This business intelligence project transforms raw game telemetry extracted via the PokéAPI into a structured analytics portal. The analytical objective is to enable deep comparison of attribute distributions, benchmark generation stat creep, and uncover dominant type combinations.
Dataset Source & API Service
Access PokéAPI PortalGenerations 1 through 9 (Kanto to Paldea)
1,025 Unique Species, 18 Elemental Types & 6 Combat Base Stats (HP/Atk/Def/SpA/SpD/Spe)
Stakeholder Decision Personas
- Game Balance Designers: Benchmark stat distributions to identify unintended power creep or under-represented type combinations.
- Competitive eSports Coaches & Players: Analyze speed tiers, defense/HP durability ratios, and offensive coverage synergies.
- Gaming Enthusiasts & Researchers: Explore visual distributions and filter specific evolutionary lines effortlessly.
BI Dimensional Architecture (Star Schema)
An automated Google Apps Script pipeline fetches and parses paginated JSON payloads from PokéAPI v2, storing the normalized dataset into Google Sheets before serving it to Looker Studio:
Fact_Pokemon_Stats
- PokemonID (PK) INT [1..1025]
- HP / Attack / Defense INT
- SpAttack / SpDefense INT
- Speed INT
- BaseStatTotal (BST) INT
- Height_M / Weight_KG DECIMAL(5,2)
Dim_Elemental_Type
- TypeKey (PK) INT [1..18]
- PrimaryType VARCHAR(20)
- SecondaryType VARCHAR(20) [Nullable]
- TypeCombination VARCHAR(40)
- TypeCategory ENUM(Physical, Special, Status)
Dim_Generation
- GenKey (PK) INT [1..9]
- GenerationName VARCHAR(20)
- MainRegion VARCHAR(30)
- ReleaseYear INT [1996..2022]
- NewSpeciesCount INT
Google Apps Script ETL Automation
Custom JavaScript ETL script running on Google Apps Script to fetch and format API records:
function fetchAndExtractPokemonData() {
var sheet = SpreadsheetApp.getActiveSpreadsheet().getActiveSheet();
var limit = 1025;
var url = 'https://pokeapi.co/api/v2/pokemon?limit=' + limit;
var response = UrlFetchApp.fetch(url);
var json = JSON.parse(response.getContentText());
var rows = [];
json.results.forEach(function(pokemon, index) {
var detailResponse = UrlFetchApp.fetch(pokemon.url);
var p = JSON.parse(detailResponse.getContentText());
var stats = {};
p.stats.forEach(function(s) { stats[s.stat.name] = s.base_stat; });
var bst = stats['hp'] + stats['attack'] + stats['defense'] +
stats['special-attack'] + stats['special-defense'] + stats['speed'];
rows.push([
p.id, p.name, p.types[0].type.name,
p.types.length > 1 ? p.types[1].type.name : 'None',
stats['hp'], stats['attack'], stats['defense'],
stats['special-attack'], stats['special-defense'], stats['speed'],
bst, p.height / 10, p.weight / 10
]);
});
sheet.getRange(2, 1, rows.length, rows[0].length).setValues(rows);
}