Introduction

This work is inspired by this paper from Elie and Celine Bursztein and will try to reproduce their findings applying some different ideas.


In [1]:
from hearthpricer import hearthpricer
import numpy
import os.path
import pandas

Cards data

Load the collectible cards inside JSON data

Let's start loading the game cards data from Hearthstone JSON and loading it to python using the json library.


In [2]:
all_sets_filename = os.path.join('data', 'AllSets.json')

# Uncomment the following lines to update the date file
#import urllib
#urllib.urlretrieve ('http://hearthstonejson.com/json/AllSets.json', all_sets_filename)

all_collectible_cards = hearthpricer.load_json(all_sets_filename)
print('# of collectible cards:', len(all_collectible_cards))


# of collectible cards: 566

Card types


In [3]:
print('Card types:', ', '.join(set((x['type'] for x in all_collectible_cards))))


Card types: Minion, Weapon, Spell

Card attributes


In [4]:
print('Card attributes:', ', '.join(set(sum((list(x.keys()) for x in all_collectible_cards), list()))))


Card attributes: name, text, playerClass, attack, cost, health, type, mechanics, durability

Card mechanics


In [5]:
print('Card mechanics:', ', '.join(
        set(sum((x['mechanics'] for x in all_collectible_cards if 'mechanics' in x), list()))))


Card mechanics: Divine Shield, AdjacentBuff, Charge, Battlecry, HealTarget, Poisonous, Taunt, Combo, Secret, Freeze, Spellpower, Windfury, ImmuneToSpellpower, Stealth, Enrage, Aura, Silence, Deathrattle, AffectedBySpellPower

The model

Card analysis will be done based on the following base model equation: $$cost = \sum (attribute_i \cdot coeff_i) + intrinsic$$ The intrinsic value represents the cost of having that card in your deck and also can be viewed as the slot_cost.

Modelling the cards

With the previous model, let's create a matrix with all the information to work with.


In [6]:
all_collectible_cards_df = pandas.DataFrame(all_collectible_cards)
all_collectible_cards_df.info()


<class 'pandas.core.frame.DataFrame'>
Int64Index: 566 entries, 0 to 565
Data columns (total 9 columns):
attack         380 non-null float64
cost           566 non-null int64
durability     18 non-null float64
health         362 non-null float64
mechanics      294 non-null object
name           566 non-null object
playerClass    324 non-null object
text           548 non-null object
type           566 non-null object
dtypes: float64(3), int64(1), object(5)
memory usage: 33.2+ KB

Vanilla minions modelling

To test the model, let's extract the coefficients for attack and health with only the minions with no text (vanilla minions).


In [7]:
vanilla_minions_df = pandas.DataFrame(
    all_collectible_cards_df[(all_collectible_cards_df['type'] == 'Minion') &
                             (all_collectible_cards_df['text'].isnull())])
vanilla_minions_df.info()


<class 'pandas.core.frame.DataFrame'>
Int64Index: 14 entries, 14 to 559
Data columns (total 9 columns):
attack         14 non-null float64
cost           14 non-null int64
durability     0 non-null float64
health         14 non-null float64
mechanics      0 non-null object
name           14 non-null object
playerClass    0 non-null object
text           0 non-null object
type           14 non-null object
dtypes: float64(3), int64(1), object(5)
memory usage: 840.0+ bytes

Let the pricing begin! Results will be stored in a new price attribute in each card. Also, coeffs are computed taking into account that a card costs: $2 \cdot cost + 1$. Although, price value will be comparable to cost.


In [8]:
vanilla_columns = ['attack', 'health']
vanilla_coeffs = hearthpricer.pricing(vanilla_minions_df, vanilla_columns, debug=True)


   intrinsic    attack    health
0  -0.501034  1.167118  0.984048

With these coeffs, we can define a ratio attribute with the ratio between the real price and cost: $$ratio = \frac{(price - intrinsic) - (cost - intrinsic)}{cost - intrinsic} = \frac{price - cost}{cost - intrinsic}$$ Then sort the results from the best to the worst in terms of ratio.


In [9]:
intrinsic = vanilla_coeffs[0][0]
vanilla_minions_df['ratio'] = (vanilla_minions_df['price'] -  vanilla_minions_df['cost']) / \
                              (vanilla_minions_df['cost'] - intrinsic)
vanilla_minions_df[['name', 'cost', 'price', 'ratio']].sort('ratio', ascending=False)


Out[9]:
name cost price ratio
559 Wisp 0 0.325066 0.648789
286 Salty Dog 5 5.302490 0.054988
258 Lost Tallstrider 4 4.135373 0.030076
18 Boulderfist Ogre 6 6.195004 0.029996
20 Chillwind Yeti 4 4.043838 0.009740
24 Core Hound 7 6.961632 -0.005115
14 Bloodfen Raptor 2 1.984207 -0.006315
281 Puddlestomper 2 1.984207 -0.006315
301 Spider Tank 3 2.968255 -0.009067
125 War Golem 7 6.778562 -0.029521
86 Oasis Snapjaw 4 3.860769 -0.030933
93 River Crocolisk 2 1.892672 -0.042913
81 Murloc Raider 1 0.908624 -0.060875
72 Magma Rager 3 2.659301 -0.097314

These results can be bad to anyone with some experience, because Wisp is listed in the first place with a great distance to the second and River Crocolisk (a good vanilla) is on the low end. But this was only a example of how the model works. More complex examples below.

Adding simple mechanics

To enrich the model, let's add simple mechanics to a minion-only matrix.

Processing the cards mechanics

Cards have to be processed to extract the card mechanics (Charge, Stealth, Windfury, Taunt, Divine Shield) from the text, adding the text_mechanics attribute with the complex mechanics. All cards with unknown mechanics are discarded when processed.


In [10]:
all_mechanics_cards = hearthpricer.process_mechanics(all_collectible_cards)
print('# of processed cards: {} ({:.2%})'.format(
        len(all_mechanics_cards), 1.0 * len(all_mechanics_cards) / len(all_collectible_cards)))


# of processed cards: 90 (15.90%)

Let's price this bunch of minions as described before.


In [11]:
all_mechanics_cards_df = pandas.DataFrame(all_mechanics_cards)
mechanics_coeffs = hearthpricer.pricing(all_mechanics_cards_df, debug=True)


   intrinsic    attack    charge    clumsy  deal_board_damage  deal_damage  \
0  -0.035427  1.155116  0.661401 -1.931062           1.075559     1.107001   

   deal_enemy_hero_damage  deal_own_hero_damage  discard_card  divine shield  \
0                0.558085             -0.543073     -2.231999       2.427762   

    elusive    health  overload  poisonous  spell_damage   stealth     taunt  \
0  0.590709  0.910626 -2.004747   2.501755      0.865698  0.897925  0.219994   

   windfury  
0  0.786156  

In [12]:
all_processed_cards = hearthpricer.process_mechanics(all_collectible_cards,
                                                     discard_unknown_mechanics=False)

text_mechanics = dict()
for card in all_processed_cards:
    if 'text_mechanics' in card:
        text_mechanics[card['text_mechanics']] = text_mechanics.get(card['text_mechanics'], 0) + 1
sorted(((y, x) for x, y in text_mechanics.items()), reverse=True)


Out[12]:
[(6, 'Spell Damage +1'),
 (3, 'Destroy any minion damaged by this minion'),
 (3, "Can't be targeted by spells or Hero Powers"),
 (2, 'Freeze any character damaged by this minion'),
 (2, 'Enrage: +3 Attack'),
 (2, 'Costs (1) less for each minion that died this turn'),
 (2, 'Battlecry: Silence a minion'),
 (2, 'Battlecry: Return a friendly minion from the battlefield to your hand'),
 (2, 'Battlecry: Give a minion +2 Attack this turn'),
 (2, 'Battlecry: Draw a card'),
 (2, 'Battlecry: Deal 1 damage'),
 (2, 'At the end of your turn give another random friendly minion +1 Health'),
 (2, '50% chance to attack the wrong enemy'),
 (1, 'Your spells cost (1) less'),
 (1, 'Your other minions have +1/+1'),
 (1, 'Your other minions have +1 Attack'),
 (1, 'Your other Pirates have +1/+1'),
 (1, 'Your other Demons have +2/+2 Your hero is Immune'),
 (1, 'Your other Beasts have +1 Attack'),
 (1, 'Your minions trigger their Deathrattles twice'),
 (1, 'Your minions cost (3) more'),
 (1, 'Your minions cost (2) less but not less than (1)'),
 (1, 'Your cards and powers that restore Health now deal damage instead'),
 (1, 'Your Mechs cost (1) less'),
 (1, 'Your Hero Power can target minions'),
 (1, 'Your Beasts have Charge'),
 (1,
  'Whenever your opponent plays a card discard the top 3 cards of your deck'),
 (1, 'Whenever your opponent casts a spell summon a Burly Rockjaw Trogg'),
 (1,
  'Whenever your opponent casts a spell gain a copy of it and give them a Coin'),
 (1, 'Whenever your opponent casts a spell gain +2 Attack'),
 (1, 'Whenever your opponent casts a spell gain +1 Attack'),
 (1, 'Whenever your hero takes damage on your turn gain +2/+2'),
 (1, 'Whenever you target this minion with a spell gain +1/+1'),
 (1, 'Whenever you summon a minion with Deathrattle gain +1 Attack'),
 (1, 'Whenever you summon a minion with 3 or less Attack give it Charge'),
 (1, 'Whenever you summon a Pirate gain Stealth'),
 (1, 'Whenever you summon a Pirate deal 2 damage to a random enemy'),
 (1, 'Whenever you summon a Mech gain Divine Shield'),
 (1, 'Whenever you summon a Beast draw a card'),
 (1, 'Whenever you play a card with Overload gain +1/+1'),
 (1, 'Whenever you play a card summon a 2/1 Flame of Azzinoth'),
 (1, 'Whenever you play a card gain +1/+1'),
 (1, 'Whenever you play a 1-Attack minion give it +2/+2'),
 (1, 'Whenever you gain Armor give this minion +1 Attack'),
 (1, 'Whenever you draw a card put another copy into your hand'),
 (1, 'Whenever you cast a spell summon a 1/1 Violet Apprentice'),
 (1, 'Whenever you cast a spell gain +2 Attack this turn'),
 (1, 'Whenever you cast a spell gain +1 Attack'),
 (1, 'Whenever you cast a spell draw a card'),
 (1, "Whenever you cast a spell add a 'Fireball' spell to your hand"),
 (1, 'Whenever you cast a 1-mana spell add a random Mech to your hand'),
 (1, 'Whenever this minion takes damage summon a 2/1 Whelp'),
 (1, 'Whenever this minion takes damage summon a 1/1 Imp'),
 (1, 'Whenever this minion takes damage gain +3 Attack'),
 (1, 'Whenever this minion takes damage draw a card'),
 (1, 'Whenever this minion takes damage double its Attack'),
 (1, 'Whenever this minion takes damage deal 2 damage to the enemy hero'),
 (1, 'Whenever this minion takes damage add a Spare Part card to your hand'),
 (1, 'Whenever this minion survives damage summon another Grim Patron'),
 (1,
  'Whenever this minion deals damage restore that much Health to your hero'),
 (1, 'Whenever one of your other minions dies draw a card'),
 (1, 'Whenever another friendly Murloc dies draw a card Overload: (1)'),
 (1, 'Whenever an enemy minion dies summon a Leper Gnome'),
 (1,
  'Whenever a player casts a spell put a copy into the other player’s hand'),
 (1, 'Whenever a minion takes damage gain +1 Attack'),
 (1, 'Whenever a minion is healed draw a card'),
 (1, 'Whenever a minion dies gain +1 Attack'),
 (1, 'Whenever a friendly minion takes damage gain 1 Armor'),
 (1,
  'Whenever a friendly minion dies while this is in your hand gain +1 Attack'),
 (1, 'Whenever a friendly Mech dies gain +2/+2'),
 (1, 'Whenever a friendly Beast dies gain +2/+1'),
 (1, 'Whenever a character is healed gain +2 Attack'),
 (1, 'Whenever a character is healed deal 1 damage to a random enemy'),
 (1, 'Whenever a Secret is played gain +1/+1'),
 (1, 'Whenever a Murloc is summoned gain +1 Attack'),
 (1, 'When you draw this deal 2 damage to all characters'),
 (1, "This minion's Attack is always equal to its Health"),
 (1, 'The first minion you play each turn costs (1) less'),
 (1, 'Spell Damage +5'),
 (1, 'Spell Damage +1 Deathrattle: Draw a card'),
 (1, 'Spell Damage +1 Battlecry: Draw a card'),
 (1, 'Players only have 15 seconds to take their turns'),
 (1, 'Overload: (3)'),
 (1, 'Overload: (2)'),
 (1, 'Overload: (1) 50% chance to attack the wrong enemy'),
 (1, 'Minions with Battlecry cost (2) more'),
 (1,
  'If you control a Secret at the end of your turn restore 4 health to your hero'),
 (1, 'If you control a Secret at the end of your turn gain +2/+2'),
 (1, 'Has Charge while you have a weapon equipped'),
 (1, 'Has +4 Attack while your opponent has 6 or more cards in hand'),
 (1, 'Has +2 Attack while you have a Mech'),
 (1, 'Has +1 Attack for each other Murloc on the battlefield'),
 (1, 'Enrage: Your weapon has +2 Attack'),
 (1, 'Enrage: Windfury and +1 Attack'),
 (1, 'Enrage: +6 Attack'),
 (1, 'Enrage: +5 Attack'),
 (1, 'Enrage: +1 Attack'),
 (1, 'Double the damage and healing of your spells and Hero Power'),
 (1,
  'Deathrattle: Your opponent puts a minion from their deck into the battlefield'),
 (1, 'Deathrattle: Your opponent draws a card'),
 (1, 'Deathrattle: Take control of a random enemy minion'),
 (1, 'Deathrattle: Summon two 2/2 Hyenas'),
 (1, 'Deathrattle: Summon two 1/1 Spectral Spiders'),
 (1, 'Deathrattle: Summon a random legendary minion'),
 (1, 'Deathrattle: Summon a random 4-Cost minion'),
 (1, 'Deathrattle: Summon a random 2-Cost minion'),
 (1, 'Deathrattle: Summon a 4/5 Baine Bloodhoof'),
 (1, 'Deathrattle: Summon a 4/4 Nerubian'),
 (1, 'Deathrattle: Summon a 3/3 Finkle Einhorn for your opponent'),
 (1, 'Deathrattle: Summon a 2/1 Damaged Golem'),
 (1, 'Deathrattle: Summon a 1/2 Slime with Taunt'),
 (1, 'Deathrattle: Shuffle this minion into your deck'),
 (1, 'Deathrattle: Return a random friendly minion to your hand'),
 (1, 'Deathrattle: Restore 5 Health to the enemy hero'),
 (1, 'Deathrattle: Replace your hero with Ragnaros the Firelord'),
 (1, 'Deathrattle: Put a random Demon from your hand into the battlefield'),
 (1, 'Deathrattle: Put a Secret from your deck into the battlefield'),
 (1, 'Deathrattle: If Stalagg also died this game summon Thaddius'),
 (1, 'Deathrattle: If Feugen also died this game summon Thaddius'),
 (1, 'Deathrattle: Give each player a Spare Part'),
 (1, 'Deathrattle: Give a random friendly minion +3 Health'),
 (1, 'Deathrattle: Equip a 5/3 Ashbringer'),
 (1, 'Deathrattle: Draw a card'),
 (1, 'Deathrattle: Deal 2 damage to the enemy hero'),
 (1, 'Deathrattle: Deal 2 damage to all minions'),
 (1, 'Deathrattle: Deal 2 damage to ALL characters'),
 (1, 'Deathrattle: Deal 1 damage to all minions'),
 (1, 'Deathrattle: Add a random Beast card to your hand'),
 (1, 'Deathrattle: Add a Spare Part card to your hand'),
 (1, 'Costs (1) less per Attack of your weapon'),
 (1, 'Costs (1) less for each other minion on the battlefield'),
 (1, 'Costs (1) less for each other card in your hand'),
 (1, 'Costs (1) less for each damage your hero has taken'),
 (1, "Costs (1) less for each card in your opponent's hand"),
 (1, 'Combo: Summon a 2/1 Defias Bandit'),
 (1, "Combo: Return a minion to its owner's hand"),
 (1, 'Combo: Gain +2/+2 for each card played earlier this turn'),
 (1, 'Combo: Deal 2 damage'),
 (1, 'Choose One - Transform into a 5/2 minion; or a 2/5 minion'),
 (1,
  'Choose One - Give your other minions +2/+2; or Summon two 2/2 Treants with Taunt'),
 (1,
  'Choose One - Give each player a Mana Crystal; or Each player draws a card'),
 (1, 'Choose One - Draw 2 cards; or Restore 5 Health'),
 (1, 'Choose One - Deal 2 damage; or Silence a minion'),
 (1, 'Choose One - Charge; or +2 Health and Taunt'),
 (1, 'Choose One - +5 Attack; or +5 Health and Taunt'),
 (1, 'Choose One - +1 Attack; or +1 Health'),
 (1, "Can't Attack At the end of your turn deal 8 damage to a random enemy"),
 (1, "Can't Attack"),
 (1,
  'Battlecry: Transform another random minion into a 5/5 Devilsaur or a 1/1 Squirrel'),
 (1,
  'Battlecry: Transform a friendly minion into a random minion with the same Cost'),
 (1, 'Battlecry: The next Secret you play this turn costs (0)'),
 (1, 'Battlecry: The next Dragon you play costs (2) less'),
 (1, 'Battlecry: Take control of an enemy minion that has 2 or less Attack'),
 (1, 'Battlecry: Take control of a random enemy Secret'),
 (1, 'Battlecry: Swap the Attack and Health of a minion'),
 (1, 'Battlecry: Swap Health with another minion'),
 (1, 'Battlecry: Summon two 1/1 Whelps for your opponent'),
 (1, 'Battlecry: Summon two 1/1 Boom Bots WARNING: Bots may explode'),
 (1, 'Battlecry: Summon an exact copy of this minion at the end of the turn'),
 (1, 'Battlecry: Summon an AWESOME invention'),
 (1, 'Battlecry: Summon a random 1-Cost minion for your opponent'),
 (1, 'Battlecry: Summon a 2/2 Squire'),
 (1, 'Battlecry: Summon a 2/1 Mechanical Dragonling'),
 (1, 'Battlecry: Summon a 1/1 Murloc Scout'),
 (1, 'Battlecry: Summon a 1/1 Boar'),
 (1,
  'Battlecry: Summon 1/1 Whelps until your side of the battlefield is full'),
 (1, 'Battlecry: Silence your other minions'),
 (1,
  "Battlecry: Shuffle a Mine into your opponent's deck When drawn it explodes for 10 damage"),
 (1, "Battlecry: Set a hero's remaining Health to 15"),
 (1, 'Battlecry: Restore 8 Health to your hero'),
 (1, 'Battlecry: Restore 6 Health to your hero'),
 (1, 'Battlecry: Restore 4 Health to your hero'),
 (1, 'Battlecry: Restore 3 Health'),
 (1, 'Battlecry: Restore 2 Health to all friendly characters'),
 (1, 'Battlecry: Restore 2 Health'),
 (1, "Battlecry: Remove 1 Durability from your opponent's weapon"),
 (1, 'Battlecry: Put a random Pirate from your deck into your hand'),
 (1,
  'Battlecry: If your opponent has 4 or more minions take control of one at random'),
 (1, 'Battlecry: If your opponent has 15 or less Health gain +3/+3'),
 (1, 'Battlecry: If your hand is empty gain +3/+3'),
 (1, "Battlecry: If you're holding a Dragon gain +2 Health"),
 (1, "Battlecry: If you're holding a Dragon gain +1/+1"),
 (1, "Battlecry: If you're holding a Dragon destroy a Legendary minion"),
 (1, "Battlecry: If you're holding a Dragon deal 3 damage"),
 (1,
  'Battlecry: If you have a Mech gain +1/+1 and add a Spare Part to your hand'),
 (1,
  'Battlecry: If you have a Mech deal 4 damage randomly split among all enemies'),
 (1, 'Battlecry: If you have a Beast transform this minion into a 7/7'),
 (1, 'Battlecry: Give your weapon +1/+1'),
 (1, 'Battlecry: Give your weapon +1 Attack'),
 (1,
  'Battlecry: Give your other minions Windfury Taunt or Divine Shield (at random)'),
 (1, 'Battlecry: Give your other Mechs +2 Attack'),
 (1, 'Battlecry: Give your opponent a Mana Crystal'),
 (1, 'Battlecry: Give your opponent 2 Bananas'),
 (1, 'Battlecry: Give your Silver Hand Recruits +2/+2'),
 (1,
  'Battlecry: Give both players the power to ROCK! (with a Power Chord card)'),
 (1, 'Battlecry: Give adjacent minions Taunt'),
 (1, 'Battlecry: Give adjacent minions Spell Damage +1'),
 (1, 'Battlecry: Give adjacent minions +1/+1 and Taunt'),
 (1, 'Battlecry: Give a minion -2 Attack this turn'),
 (1, 'Battlecry: Give a friendly minion Windfury'),
 (1, 'Battlecry: Give a friendly minion Stealth'),
 (1, 'Battlecry: Give a friendly minion Divine Shield'),
 (1, 'Battlecry: Give a friendly minion +3 Health'),
 (1, 'Battlecry: Give a friendly minion +1/+1'),
 (1, 'Battlecry: Give a friendly Mech +4 Health'),
 (1, 'Battlecry: Give a friendly Mech +2/+2'),
 (1, 'Battlecry: Give a friendly Beast +2/+2 and Taunt'),
 (1, 'Battlecry: Give ALL other Murlocs +2 Health'),
 (1, 'Battlecry: Gain Attack equal to the Attack of your weapon'),
 (1, 'Battlecry: Gain 5 Armor'),
 (1, 'Battlecry: Gain 1-4 Attack Overload: (1)'),
 (1,
  'Battlecry: Gain +1/+1 for each other friendly minion on the battlefield'),
 (1, 'Battlecry: Gain +1/+1 for each enemy Deathrattle minion'),
 (1, 'Battlecry: Gain +1 Health for each card in your hand'),
 (1, 'Battlecry: Gain +1 Attack for each other Beast you have'),
 (1, 'Battlecry: Freeze a character'),
 (1, 'Battlecry: Equip a random weapon for each player'),
 (1, 'Battlecry: Equip a 2/2 weapon'),
 (1, 'Battlecry: Enemy spells cost (5) more next turn'),
 (1, 'Battlecry: Enemy spells cost (0) next turn'),
 (1, 'Battlecry: Each player draws 2 cards'),
 (1, "Battlecry: Draw a card If it's a minion transform it into a Chicken"),
 (1, 'Battlecry: Discard two random cards'),
 (1, 'Battlecry: Discard a random card'),
 (1,
  "Battlecry: Destroy your opponent's weapon and draw cards equal to its Durability"),
 (1, "Battlecry: Destroy your opponent's weapon"),
 (1, 'Battlecry: Destroy your hero and replace it with Lord Jaraxxus'),
 (1,
  'Battlecry: Destroy the minions on either side of this minion and gain their Attack and Health'),
 (1, 'Battlecry: Destroy one of your Mana Crystals'),
 (1, 'Battlecry: Destroy an enemy minion with Taunt'),
 (1, 'Battlecry: Destroy all other minions and discard your hand'),
 (1, 'Battlecry: Destroy a random enemy minion with 2 or less Attack'),
 (1, 'Battlecry: Destroy a minion with an Attack of 7 or more'),
 (1, 'Battlecry: Destroy a Murloc and gain +2/+2'),
 (1, 'Battlecry: Destroy a Beast'),
 (1, 'Battlecry: Deal 6 damage randomly split between all other characters'),
 (1, 'Battlecry: Deal 5 damage to your hero'),
 (1, 'Battlecry: Deal 4 damage to a random enemy minion'),
 (1, 'Battlecry: Deal 4 damage to HIMSELF'),
 (1, 'Battlecry: Deal 3 damage to your hero'),
 (1, 'Battlecry: Deal 3 damage to the enemy hero'),
 (1, 'Battlecry: Deal 3 damage to each hero'),
 (1, 'Battlecry: Deal 3 damage randomly split between all other characters'),
 (1, 'Battlecry: Deal 3 damage'),
 (1, 'Battlecry: Deal 2 damage to all undamaged enemy minions'),
 (1, 'Battlecry: Deal 2 damage to all minions with Deathrattle'),
 (1, 'Battlecry: Deal 2 damage'),
 (1, 'Battlecry: Deal 1 damage to a minion and give it +2 Attack'),
 (1, 'Battlecry: Deal 1 damage to ALL other characters'),
 (1, 'Battlecry: Choose a minion and become a copy of it'),
 (1, "Battlecry: Change an enemy minion's Attack to 1"),
 (1,
  'Battlecry: All minions lose Divine Shield Gain +3/+3 for each Shield lost'),
 (1, 'Battlecry: Add 4 random Murlocs to your hand Overload: (3)'),
 (1,
  "Battlecry: Add 2 random spells to your hand (from your opponent's class)"),
 (1, 'Battlecry and Deathrattle: Add a Spare Part card to your hand'),
 (1, 'At the start of your turn you have a 50% chance to draw an extra card'),
 (1,
  'At the start of your turn swap this minion with a random one in your hand'),
 (1, 'At the start of your turn restore this minion to full Health'),
 (1,
  'At the start of your turn restore 3 Health to a damaged friendly character'),
 (1,
  'At the start of your turn if you have at least 3 Mechs destroy them all and form V-07-TR-0N'),
 (1, 'At the start of your turn gain +1/+1'),
 (1, 'At the start of your turn destroy ALL minions'),
 (1, 'At the start of your turn deal 2 damage to a random enemy'),
 (1, 'At the start of each turn gain +1 Attack'),
 (1, 'At the end of your turn summon a 2/2 Gnoll with Taunt'),
 (1, 'At the end of your turn restore 4 Health to your hero'),
 (1, 'At the end of your turn reduce the Cost of cards in your hand by (1)'),
 (1, 'At the end of your turn give another random friendly minion +1 Attack'),
 (1, 'At the end of your turn give another friendly Mech +2/+2'),
 (1, 'At the end of your turn draw a card'),
 (1, 'At the end of your turn deal 2 damage to a non-Mech minion'),
 (1, 'At the end of your turn deal 2 damage to ALL other characters'),
 (1,
  'At the end of your turn deal 1 damage to this minion and summon a 1/1 Imp'),
 (1, 'At the end of your turn add a Dream Card to your hand'),
 (1,
  'At the end of each turn summon all friendly minions that died this turn'),
 (1, 'At the end of each turn gain +1/+1'),
 (1, "At the end of each turn destroy this minion if it's your only one"),
 (1,
  "At the end of each player's turn that player draws until they have 3 cards"),
 (1, 'Also damages the minions next to whomever he attacks'),
 (1, 'All minions have a 50% chance to attack the wrong enemy'),
 (1, 'After you summon a minion deal 1 damage to a random enemy'),
 (1, 'After you cast a spell deal 2 damage randomly split among all enemies'),
 (1, 'After you cast a spell deal 1 damage to ALL minions'),
 (1, 'Adjacent minions have +2 Attack'),
 (1, 'Adjacent minions have +1 Attack'),
 (1, "Adjacent minions can't be targeted by spells or Hero Powers"),
 (1, 'ALL other Murlocs have +2/+1'),
 (1, 'ALL other Murlocs have +1 Attack'),
 (1, 'ALL minions cost (1) more')]

In [13]:
intrinsic = vanilla_coeffs[0][0]
all_mechanics_cards_df['ratio'] = (all_mechanics_cards_df['price'] -  all_mechanics_cards_df['cost']) / \
                                  (all_mechanics_cards_df['cost'] + intrinsic)
results_df = all_mechanics_cards_df[['playerClass', 'name', 'cost', 'price', 'ratio']].sort(
    'ratio', ascending=False)

Results

0 cost minions


In [14]:
results_df[results_df.cost == 0]


Out[14]:
playerClass name cost price ratio
87 NaN Wisp 0 0.515157 -1.028188
58 NaN Target Dummy 0 0.612906 -1.223283

1 cost minions


In [15]:
results_df[results_df.cost == 1]


Out[15]:
playerClass name cost price ratio
32 Warlock Voidwalker 1 1.755775 1.514683
81 NaN Shieldbearer 1 1.743527 1.490136
63 NaN Argent Squire 1 1.729039 1.461099
73 NaN Leper Gnome 1 1.650801 1.304300
88 NaN Worgen Infiltrator 1 1.541678 1.085602
70 Warlock Flame Imp 1 1.310977 0.623244
12 NaN Goldshire Footman 1 1.190465 0.381719
54 Priest Shadowbomber 1 1.115234 0.230946
20 NaN Murloc Raider 1 1.092716 0.185815
9 NaN Elven Archer 1 1.068658 0.137601
89 NaN Young Dragonhawk 1 0.908236 -0.183909
28 NaN Stonetusk Boar 1 0.845858 -0.308924
65 Shaman Dust Devil 1 0.844761 -0.311121

2 cost minions


In [16]:
results_df[results_df.cost == 2]


Out[16]:
playerClass name cost price ratio
59 Shaman Whirling Zap-o-matic 2 3.304821 0.870481
55 Paladin Shielded Minibot 2 2.761910 0.508290
45 NaN Gilblin Stalker 2 2.452304 0.301744
68 NaN Faerie Dragon 2 2.420941 0.280821
38 NaN Annoy-o-Tron 2 2.404346 0.269750
37 NaN Unstable Ghoul 2 2.293554 0.195838
76 Rogue Patient Assassin 2 2.214997 0.143430
1 NaN Bloodfen Raptor 2 2.125587 0.083782
51 NaN Puddlestomper 2 2.125587 0.083782
31 Warlock Succubus 2 2.042458 0.028325
25 NaN River Crocolisk 2 2.003342 0.002229
16 NaN Kobold Geomancer 2 1.980878 -0.012757
11 NaN Frostwolf Grunt 2 1.768023 -0.154758
2 NaN Bluegill Warrior 2 1.754116 -0.164036
42 NaN Explosive Sheep 2 1.590716 -0.273044

3 cost minions


In [17]:
results_df[results_df.cost == 3]


Out[17]:
playerClass name cost price ratio
79 Rogue SI:7 Agent 3 3.687901 0.275274
53 Paladin Scarlet Purifier 3 3.259991 0.104039
67 NaN Emperor Cobra 3 3.254219 0.101730
71 NaN Jungle Panther 3 3.152107 0.060868
57 NaN Spider Tank 3 3.036213 0.014491
56 Mage Soot Spewer 3 3.013749 0.005502
15 NaN Ironfur Grizzly 3 2.910891 -0.035658
80 NaN Scarlet Crusader 3 2.884155 -0.046357
19 NaN Magma Rager 3 2.825390 -0.069873
85 NaN Thrallmar Farseer 3 2.789498 -0.084236
34 NaN Wolfrider 3 2.662375 -0.135106
46 NaN Gnomeregan Infantry 3 2.651785 -0.139344
49 NaN Ogre Brute 3 2.648240 -0.140762
27 NaN Silverback Patriarch 3 2.321085 -0.271678
7 NaN Dalaran Mage 3 2.313946 -0.274535
43 NaN Flying Machine 3 2.274175 -0.290450
14 NaN Ironforge Rifleman 3 2.101529 -0.359537

4 cost minions


In [18]:
results_df[results_df.cost == 4]


Out[18]:
playerClass name cost price ratio
17 Warrior Kor'kron Elite 4 4.481259 0.137543
47 NaN Lost Tallstrider 4 4.191329 0.054682
41 Shaman Dunemaul Shaman 4 4.188815 0.053963
5 NaN Chillwind Yeti 4 4.069084 0.019744
23 NaN Ogre Magi 4 4.046620 0.013324
26 NaN Sen'jin Shieldmasta 4 4.041511 0.011864
75 NaN Mogu'shan Warden 4 4.017015 0.004863
22 NaN Oasis Snapjaw 4 3.824594 -0.050131
82 NaN Silvermoon Guardian 4 3.794781 -0.058651
39 NaN Arcane Nullifier X-21 4 3.759308 -0.068790
77 Warlock Pit Lord 4 3.744273 -0.073086
30 NaN Stormwind Knight 4 3.575368 -0.121359
48 NaN Mini-Mage 4 3.129644 -0.248747

5 cost minions


In [19]:
results_df[results_df.cost == 5]


Out[19]:
playerClass name cost price ratio
52 NaN Salty Dog 5 5.346445 0.077006
50 Rogue Ogre Ninja 5 5.162945 0.036218
60 NaN Abomination 5 5.129318 0.028744
83 NaN Stranglethorn Tiger 5 5.095604 0.021250
66 Shaman Earth Elemental 5 5.040554 0.009014
64 Warlock Doomguard 5 4.978771 -0.004719
36 NaN Spectral Knight 5 4.819751 -0.040064
40 NaN Bomb Lobber 5 4.794903 -0.045588
3 NaN Booty Bay Bodyguard 5 4.631317 -0.081948
69 NaN Fen Creeper 5 4.606821 -0.087393
21 NaN Nightblade 5 4.450899 -0.122051
29 NaN Stormpike Commando 5 3.810146 -0.264473

6 cost minions


In [20]:
results_df[results_df.cost == 6]


Out[20]:
playerClass name cost price ratio
10 Shaman Fire Elemental 6 6.884702 0.160885
8 Warlock Dread Infernal 6 6.217292 0.039515
4 NaN Boulderfist Ogre 6 6.134826 0.024518
84 NaN Sunwalker 6 5.832950 -0.030378
18 NaN Lord of the Arena 6 5.774186 -0.041065
86 NaN Windfury Harpy 6 5.641397 -0.065213
35 NaN Maexxna 6 5.530784 -0.085328
0 NaN Archmage 6 5.412559 -0.106828
62 NaN Argent Commander 6 5.239827 -0.138239
24 NaN Reckless Rocketeer 6 4.934204 -0.193817

7+ cost minions


In [21]:
results_df[results_df.cost >= 7]


Out[21]:
playerClass name cost price ratio
72 Hunter King Krush 9 10.390858 0.163650
13 Druid Ironbark Protector 8 8.625232 0.083376
74 NaN Malygos 9 9.420521 0.049479
6 NaN Core Hound 7 6.956875 -0.006636
44 NaN Force-Tank MAX 8 7.926265 -0.009833
33 NaN War Golem 7 6.712384 -0.044256
61 Shaman Al'Akir the Windlord 8 7.426728 -0.076447
78 NaN Ravenholdt Assassin 7 6.250721 -0.115292

Some statistics

Deathrattle minions

Let's see what is the expected damage from a Scarlet Purifier.


In [22]:
minions_df = all_collectible_cards_df[(all_collectible_cards_df['type'] == 'Minion')]
deathrattle_minions_df = minions_df[minions_df.apply(
        lambda x: x['mechanics'] is not numpy.nan and ('Deathrattle' in x['mechanics']), axis=1)]

print('Deathrattle minions population: {} ({:.2%})'.format(
        len(deathrattle_minions_df), 1.0 * len(deathrattle_minions_df) / len(minions_df)))


Deathrattle minions population: 33 (9.12%)

2-cost minions

Let's see what is the expected minion stats from a Piloted Shredder deathrattle.


In [23]:
two_cost_minions_df = all_collectible_cards_df[(all_collectible_cards_df['type'] == 'Minion') &
                                               (all_collectible_cards_df['cost'] == 2)]
print('2-cost minion mean attack: {:.2f}'.format(two_cost_minions_df.attack.mean()))
print('2-cost minion mean health: {:.2f}'.format(two_cost_minions_df.health.mean()))


2-cost minion mean attack: 1.88
2-cost minion mean health: 2.45