Home > Net >  How to add a single legend label for each group of lines
How to add a single legend label for each group of lines

Time:10-23

I am working with a data set on Pokemon from pokemon parallel coordinates

I would like the labels to correspond to the color dictionary defined above, and it looks like I currently have repeating colors. TIA!

Data

  • Subset of data. Only 'Generation' == 1 and relevant columns
HP,Attack,Defense,Sp. Atk,Sp. Def,Speed,Name,Type 1,Generation
45,49,49,65,65,45,Bulbasaur,Grass,1
60,62,63,80,80,60,Ivysaur,Grass,1
80,82,83,100,100,80,Venusaur,Grass,1
80,100,123,122,120,80,VenusaurMega Venusaur,Grass,1
39,52,43,60,50,65,Charmander,Fire,1
58,64,58,80,65,80,Charmeleon,Fire,1
78,84,78,109,85,100,Charizard,Fire,1
78,130,111,130,85,100,CharizardMega Charizard X,Fire,1
78,104,78,159,115,100,CharizardMega Charizard Y,Fire,1
44,48,65,50,64,43,Squirtle,Water,1
59,63,80,65,80,58,Wartortle,Water,1
79,83,100,85,105,78,Blastoise,Water,1
79,103,120,135,115,78,BlastoiseMega Blastoise,Water,1
45,30,35,20,20,45,Caterpie,Bug,1
50,20,55,25,25,30,Metapod,Bug,1
60,45,50,90,80,70,Butterfree,Bug,1
40,35,30,20,20,50,Weedle,Bug,1
45,25,50,25,25,35,Kakuna,Bug,1
65,90,40,45,80,75,Beedrill,Bug,1
65,150,40,15,80,145,BeedrillMega Beedrill,Bug,1
40,45,40,35,35,56,Pidgey,Normal,1
63,60,55,50,50,71,Pidgeotto,Normal,1
83,80,75,70,70,101,Pidgeot,Normal,1
83,80,80,135,80,121,PidgeotMega Pidgeot,Normal,1
30,56,35,25,35,72,Rattata,Normal,1
55,81,60,50,70,97,Raticate,Normal,1
40,60,30,31,31,70,Spearow,Normal,1
65,90,65,61,61,100,Fearow,Normal,1
35,60,44,40,54,55,Ekans,Poison,1
60,85,69,65,79,80,Arbok,Poison,1
35,55,40,50,50,90,Pikachu,Electric,1
60,90,55,90,80,110,Raichu,Electric,1
50,75,85,20,30,40,Sandshrew,Ground,1
75,100,110,45,55,65,Sandslash,Ground,1
55,47,52,40,40,41,Nidoran♀,Poison,1
70,62,67,55,55,56,Nidorina,Poison,1
90,92,87,75,85,76,Nidoqueen,Poison,1
46,57,40,40,40,50,Nidoran♂,Poison,1
61,72,57,55,55,65,Nidorino,Poison,1
81,102,77,85,75,85,Nidoking,Poison,1
70,45,48,60,65,35,Clefairy,Fairy,1
95,70,73,95,90,60,Clefable,Fairy,1
38,41,40,50,65,65,Vulpix,Fire,1
73,76,75,81,100,100,Ninetales,Fire,1
115,45,20,45,25,20,Jigglypuff,Normal,1
140,70,45,85,50,45,Wigglytuff,Normal,1
40,45,35,30,40,55,Zubat,Poison,1
75,80,70,65,75,90,Golbat,Poison,1
45,50,55,75,65,30,Oddish,Grass,1
60,65,70,85,75,40,Gloom,Grass,1
75,80,85,110,90,50,Vileplume,Grass,1
35,70,55,45,55,25,Paras,Bug,1
60,95,80,60,80,30,Parasect,Bug,1
60,55,50,40,55,45,Venonat,Bug,1
70,65,60,90,75,90,Venomoth,Bug,1
10,55,25,35,45,95,Diglett,Ground,1
35,80,50,50,70,120,Dugtrio,Ground,1
40,45,35,40,40,90,Meowth,Normal,1
65,70,60,65,65,115,Persian,Normal,1
50,52,48,65,50,55,Psyduck,Water,1
80,82,78,95,80,85,Golduck,Water,1
40,80,35,35,45,70,Mankey,Fighting,1
65,105,60,60,70,95,Primeape,Fighting,1
55,70,45,70,50,60,Growlithe,Fire,1
90,110,80,100,80,95,Arcanine,Fire,1
40,50,40,40,40,90,Poliwag,Water,1
65,65,65,50,50,90,Poliwhirl,Water,1
90,95,95,70,90,70,Poliwrath,Water,1
25,20,15,105,55,90,Abra,Psychic,1
40,35,30,120,70,105,Kadabra,Psychic,1
55,50,45,135,95,120,Alakazam,Psychic,1
55,50,65,175,95,150,AlakazamMega Alakazam,Psychic,1
70,80,50,35,35,35,Machop,Fighting,1
80,100,70,50,60,45,Machoke,Fighting,1
90,130,80,65,85,55,Machamp,Fighting,1
50,75,35,70,30,40,Bellsprout,Grass,1
65,90,50,85,45,55,Weepinbell,Grass,1
80,105,65,100,70,70,Victreebel,Grass,1
40,40,35,50,100,70,Tentacool,Water,1
80,70,65,80,120,100,Tentacruel,Water,1
40,80,100,30,30,20,Geodude,Rock,1
55,95,115,45,45,35,Graveler,Rock,1
80,120,130,55,65,45,Golem,Rock,1
50,85,55,65,65,90,Ponyta,Fire,1
65,100,70,80,80,105,Rapidash,Fire,1
90,65,65,40,40,15,Slowpoke,Water,1
95,75,110,100,80,30,Slowbro,Water,1
95,75,180,130,80,30,SlowbroMega Slowbro,Water,1
25,35,70,95,55,45,Magnemite,Electric,1
50,60,95,120,70,70,Magneton,Electric,1
52,65,55,58,62,60,Farfetch'd,Normal,1
35,85,45,35,35,75,Doduo,Normal,1
60,110,70,60,60,100,Dodrio,Normal,1
65,45,55,45,70,45,Seel,Water,1
90,70,80,70,95,70,Dewgong,Water,1
80,80,50,40,50,25,Grimer,Poison,1
105,105,75,65,100,50,Muk,Poison,1
30,65,100,45,25,40,Shellder,Water,1
50,95,180,85,45,70,Cloyster,Water,1
30,35,30,100,35,80,Gastly,Ghost,1
45,50,45,115,55,95,Haunter,Ghost,1
60,65,60,130,75,110,Gengar,Ghost,1
60,65,80,170,95,130,GengarMega Gengar,Ghost,1
35,45,160,30,45,70,Onix,Rock,1
60,48,45,43,90,42,Drowzee,Psychic,1
85,73,70,73,115,67,Hypno,Psychic,1
30,105,90,25,25,50,Krabby,Water,1
55,130,115,50,50,75,Kingler,Water,1
40,30,50,55,55,100,Voltorb,Electric,1
60,50,70,80,80,140,Electrode,Electric,1
60,40,80,60,45,40,Exeggcute,Grass,1
95,95,85,125,65,55,Exeggutor,Grass,1
50,50,95,40,50,35,Cubone,Ground,1
60,80,110,50,80,45,Marowak,Ground,1
50,120,53,35,110,87,Hitmonlee,Fighting,1
50,105,79,35,110,76,Hitmonchan,Fighting,1
90,55,75,60,75,30,Lickitung,Normal,1
40,65,95,60,45,35,Koffing,Poison,1
65,90,120,85,70,60,Weezing,Poison,1
80,85,95,30,30,25,Rhyhorn,Ground,1
105,130,120,45,45,40,Rhydon,Ground,1
250,5,5,35,105,50,Chansey,Normal,1
65,55,115,100,40,60,Tangela,Grass,1
105,95,80,40,80,90,Kangaskhan,Normal,1
105,125,100,60,100,100,KangaskhanMega Kangaskhan,Normal,1
30,40,70,70,25,60,Horsea,Water,1
55,65,95,95,45,85,Seadra,Water,1
45,67,60,35,50,63,Goldeen,Water,1
80,92,65,65,80,68,Seaking,Water,1
30,45,55,70,55,85,Staryu,Water,1
60,75,85,100,85,115,Starmie,Water,1
40,45,65,100,120,90,Mr. Mime,Psychic,1
70,110,80,55,80,105,Scyther,Bug,1
65,50,35,115,95,95,Jynx,Ice,1
65,83,57,95,85,105,Electabuzz,Electric,1
65,95,57,100,85,93,Magmar,Fire,1
65,125,100,55,70,85,Pinsir,Bug,1
65,155,120,65,90,105,PinsirMega Pinsir,Bug,1
75,100,95,40,70,110,Tauros,Normal,1
20,10,55,15,20,80,Magikarp,Water,1
95,125,79,60,100,81,Gyarados,Water,1
95,155,109,70,130,81,GyaradosMega Gyarados,Water,1
130,85,80,85,95,60,Lapras,Water,1
48,48,48,48,48,48,Ditto,Normal,1
55,55,50,45,65,55,Eevee,Normal,1
130,65,60,110,95,65,Vaporeon,Water,1
65,65,60,110,95,130,Jolteon,Electric,1
65,130,60,95,110,65,Flareon,Fire,1
65,60,70,85,75,40,Porygon,Normal,1
35,40,100,90,55,35,Omanyte,Rock,1
70,60,125,115,70,55,Omastar,Rock,1
30,80,90,55,45,55,Kabuto,Rock,1
60,115,105,65,70,80,Kabutops,Rock,1
80,105,65,60,75,130,Aerodactyl,Rock,1
80,135,85,70,95,150,AerodactylMega Aerodactyl,Rock,1
160,110,65,65,110,30,Snorlax,Normal,1
90,85,100,95,125,85,Articuno,Ice,1
90,90,85,125,90,100,Zapdos,Electric,1
90,100,90,125,85,90,Moltres,Fire,1
41,64,45,50,50,50,Dratini,Dragon,1
61,84,65,70,70,70,Dragonair,Dragon,1
91,134,95,100,100,80,Dragonite,Dragon,1
106,110,90,154,90,130,Mewtwo,Psychic,1
106,190,100,154,100,130,MewtwoMega Mewtwo X,Psychic,1
106,150,70,194,120,140,MewtwoMega Mewtwo Y,Psychic,1
100,100,100,100,100,100,Mew,Psychic,1

CodePudding user response:

Here's how you can do this:

import pandas as pd
import matplotlib.pyplot as plt
plt.rcParams["figure.figsize"] = (10,10)
df = pd.read_csv("/kaggle/input/pokemon/Pokemon.csv")
COLORS = {'Normal' : '#AAAA77',
     'Fire': '#ff4422',
     'Water': '#3399ff',
     'Electric': '#ffcc33',
     'Grass': '#77cc55',
     'Ice': '#66ccff',
     'Fighting': '#bb5544',
     'Poison': '#aa5599',
     'Ground': '#ddbb55',
     'Flying': '#8899ff',
     'Psychic': '#ff5599',
     'Bug': '#aabb22',
     'Rock': '#bbaa66',
     'Ghost': '#6666bb',
     'Dragon': '#7766ee',
     'Dark': '#775544',
     'Steel': '#aaaabb',
     'Fairy': '#ee99ee'}
df = df.loc[df["Generation"] == 1]
colors = [COLORS.get(i) for i in df["Type 1"]]
df.index = colors
# In gen 1 special attack and special defense are the same
df.rename(columns={"Sp. Atk":"Special"}, inplace=True) 
ax = df[["HP", "Attack", "Defense", "Special", "Speed"]].T.plot(color=colors)
# Remove duplicate legend labels, but maintain colors
l, d = [], set()
for t, line in zip(plt.legend().texts, plt.legend().legendHandles):
    if t.__repr__() in d:
        continue
    d.add(t.__repr__())
    l.append((t, line))
ax.get_legend().remove()
flip_colors = {v:k for k, v in COLORS.items()}
plt.legend([j[1] for j in l], [flip_colors[j[0]._text] for j in l])
plt.show()

Yields:

enter image description here

CodePudding user response:

  • The point of a visualization is to clearly convey information, so a line plot connecting discrete categories is not an appropriate visualization for this data.
    • A bar plot should be used
  • Convert the wide dataframe to a long dataframe with melt
  • Select the Generation 1 data with Boolean indexing
  • Sort the data
  • Plot the data with seaborn.catplot with kind='bar'.
    • Use colors as the palette for hue.
import seaborn as sns
import pandas as pd
import matplotlib.pyplot as plt

# load the data
df = pd.read_csv('Pokemon.csv')

# specify colors
colors = {'Normal': '#AAAA77', 'Fire': '#ff4422', 'Water': '#3399ff', 'Electric': '#ffcc33', 'Grass': '#77cc55',
          'Ice': '#66ccff', 'Fighting': '#bb5544', 'Poison': '#aa5599', 'Ground': '#ddbb55', 'Flying': '#8899ff',
          'Psychic': '#ff5599', 'Bug': '#aabb22', 'Rock': '#bbaa66', 'Ghost': '#6666bb', 'Dragon': '#7766ee',
          'Dark': '#775544', 'Steel': '#aaaabb', 'Fairy': '#ee99ee'}

# create a list of the stats columns
cols = ['HP', 'Attack', 'Defense', 'Sp. Atk', 'Sp. Def', 'Speed']

# melt the dataframe into a long form
sel = df.melt(id_vars=['Name', 'Type 1', 'Generation'], value_vars=cols, var_name='Stats')

# for this example select only generation 1 and sort by Type 1 and Name
g1 = sel[sel.Generation.eq(1)].sort_values(['Type 1', 'Name'])

# plot with seaborn, a high-level api for matplotlib
p = sns.catplot(kind='bar', data=g1, x='Name', y='value', hue='Type 1', palette=colors, row='Stats',
                height=4, aspect=5.25, dodge=False, sharey=False)

# add xtick labels for each row if desired
for ax in p.axes.flatten():
    ax.tick_params(labelbottom=True)

# rotate the xtick labels
p.set_xticklabels(rotation=90, size=8)

# fix the layout so the full xticklabel will show
p.fig.tight_layout()

# move the legend
sns.move_legend(p, "lower center", bbox_to_anchor=(.5, 1), ncol=3, frameon=False)

# save the figure
p.savefig("pokemon.png")

# show the plot
plt.show()

enter image description here

Aggregated Plots

  • These plots show the mean of each group
sns.catplot(kind='bar', data=g1, x='Stats', y='value', hue='Type 1', palette=colors, height=4, aspect=5, ci=None)

enter image description here

sns.relplot(kind='line', data=g1, x='Stats', y='value', hue='Type 1', palette=colors, height=4, aspect=5, ci=None)

enter image description here


Plot all the lines for each 'Name'

  • I don't recommend this option because the visualization is not helpful
# use cols from previous section
cols2 = ['Name', 'Type 1', 'Generation']   cols
gen = df.loc[df.Generation.eq(1), cols2]

# create a figure
fig, ax = plt.subplots(figsize=(20, 4))

# groupby Type1
for type1, data in gen.groupby('Type 1'):
    # iterate through each row of data for the given type
    for i, row in enumerate(data.iterrows()):
        # if it's the first row, include a legend
        if i == 0:
            row[1][3:].plot(ax=ax, label=type1, color=colors[type1])
        # otherwise don't include a legend
        else:
            row[1][3:].plot(ax=ax, color=colors[type1], label='')
            
# adjust the margins
ax.margins(x=0)
# move the legend
ax.legend(bbox_to_anchor=(1.02, 1.02), loc='upper left')

enter image description here

  • Related