Grinding your way from level 41-50 is a pain but seeing players on your friend list at level 50, you wonder when will you ever be on "PoGo mile-50 club". As a data scientist, your reflex action is to google (or maybe bing) pokemon go level requirements 40-50 Or if you prefer to ask / , you would have typed: bard chatgpt What are the level requirements for pokemon go from level 40-50? We used to talk normally until we regress into communicating with computers by dropping "stopwords" and SEO our keystrokes. And now you want us to go back to typing in full sentences so that computers can try to learn human language?! Maybe those AI models should start understanding: Cut-away: (╯°□°)╯︵◓ 🪜 how? Okay, back to proper plotting... This GameRadar guide is a good start but it's just not "data-friendly", lets convert the information into a JSON. https://www.gamesradar.com/pokemon-go-level-tasks-requirements-41-50/ level_guide = { 41: { 'xp': 6_000_000, 'rewards': { 'ultra_balls': 20, 'max_potions': 20, 'max_revives': 20, 'razz_berries': 20, 'incubator': 1, 'xl_candy': 1 }, 'tasks': ['Power up a Legendary Pokemon 20 times', 'Win 30 raids', 'Catch 200 Pokemon in a single day', 'Earn 5 gold medals' ] }, 42: { 'xp': 7_000_000, 'rewards': { 'ultra_balls': 20, 'max_potions': 20, 'max_revives': 20, 'nanab_berries': 20, 'incubator': 1, 'premium_pass': 1, 'xl_candy': 1 }, 'tasks': ['Evolve Eevee into each of its unique evolutions', 'Use items to evolve Pokemon 15 times', 'Make 3 Excellent Throws', 'Use 200 berries to help catch Pokemon' ] }, ..., 50: { 'xp': 30_000_000, 'rewards': { 'ultra_balls': 50, 'max_potions': 50, 'max_revives': 20, 'super_incubator': 5, 'xl_candy': 2, 'elite_charged_tm': 1, 'incense': 5, 'lucky_egg': 5, 'lure': 5 }, 'tasks': ['Make 999 Excellent throws', 'Catch a Legendary Pokemon in your next five Legendary Pokemon encounters', 'Defeat a Team Go Rocket Leader three times using only Pokemon with 2,500 CP or less', 'Reach Rank 10 in the Go Battle League' ] }, } from pprint import pprint pprint(level_guide[42]) {'rewards': {'incubator': 1, 'max_potions': 20, 'max_revives': 20, 'nanab_berries': 20, 'premium_pass': 1, 'ultra_balls': 20, 'xl_candy': 1}, 'tasks': ['Evolve Eevee into each of its unique evolutions', 'Use items to evolve Pokemon 15 times', 'Make 3 Excellent Throws', 'Use 200 berries to help catch Pokemon'], 'xp': 7000000} Lets see how much more effort do I need to get to the next level import pandas as pd import matplotlib as mpl import matplotlib.pyplot as plt import seaborn as sns sns.set_theme(style="darkgrid") plt.ticklabel_format(style='plain') xp_needed = {k:level_guide[k]['xp'] for k in level_guide} df_xp = pd.DataFrame(xp_needed.items(), columns=['Level', 'XP']) ax = sns.lineplot(data=df_xp, x="Level", y="XP") ax.yaxis.set_major_formatter(mpl.ticker.StrMethodFormatter('{x:,.0f}')) ax.set_xlim(40, 51) plt.show() What?! It's exponential increase in the no. of XP needed?!! Oh PoGo, your game designers and devs seriously knows how to keep players "sticky" to the app and buy those ever-inflationary Pokecoins! So much sacrifice to gratify my childhood nostalgia for cute little monsters on a candy-bar phone. For my grinding effort, what juicy binary rewards will I get? pprint(level_guide[42]['rewards']) {'incubator': 1, 'max_potions': 20, 'max_revives': 20, 'nanab_berries': 20, 'premium_pass': 1, 'ultra_balls': 20, 'xl_candy': 1} Even reading the list of rewards is painful to know exactly how many pots, balls and revives I'll get after leveling up... Lets try to put them into pictures. First collect the list of image urls and automate some downloading. import shutil import requests import imageio.v2 as imageio import numpy as np import PIL from PIL import Image from IPython.display import Image as JupyterImage def download_image(img_url, save_to, thumbnail_size=(25,25)): response = requests.get(img_url, stream = True) response.raw.decode_content = True with open(save_to,'wb') as fout: shutil.copyfileobj(response.raw, fout) # Create thumbnail if thumbnail_size: image = Image.open(save_to) image.thumbnail(thumbnail_size) image.save(save_to.rpartition('.')[0] + '.thumbnail.png') img_urls = {'golden_razz_berries': "https://www.serebii.net/brilliantdiamondshiningpearl/berries/16.png", 'max_potions': 'https://static.wikia.nocookie.net/pokemon/images/a/a2/Dream_Max_Potion_Sprite.png', 'max_revives': 'https://static.wikia.nocookie.net/pokemon/images/4/45/Dream_Max_Revive_Sprite.png', 'nanab_berries': 'https://static.wikia.nocookie.net/pokemon/images/f/f6/NanabBerry-GO.png', 'razz_berries': "https://static.wikia.nocookie.net/pokemon/images/3/32/Dream_Razz_Berry_Sprite.png", 'silver_pinap_berries': 'https://pokemongohub.net/wp-content/uploads/2018/08/Item_0707.png', 'pinap_berries': 'https://pokemongohub.net/wp-content/uploads/2018/03/Item_0705.png', 'ultra_balls': 'https://static.wikia.nocookie.net/pokemon/images/f/f1/UltraBallArt.png'} for i, url in img_urls.items(): download_image(url, f'{i}.png') Ah, I can use a scatter plot and to present the rewards. add_artist From https://stackoverflow.com/questions/22566284/matplotlib-how-to-plot-images-instead-of-points def fetch_num_items(level_guide_l, x): for r in level_guide_l['rewards']: if r.endswith(x): return level_guide_l['rewards'][r] return 0 ball_pots_revs = [{'levels': l, 'potions': fetch_num_items(level_guide[l], '_potions'), 'revives': fetch_num_items(level_guide[l], '_revives'), 'balls': fetch_num_items(level_guide[l], '_balls'), 'berries': fetch_num_items(level_guide[l], '_berries'), 'balls_img': next(r for r in level_guide[l]['rewards'] if r.endswith('_balls')) + '.thumbnail.png', 'potions_img': next(r for r in level_guide[l]['rewards'] if r.endswith('_potions')) + '.thumbnail.png', 'revives_img': next(r for r in level_guide[l]['rewards'] if r.endswith('_revives')) + '.thumbnail.png', } for l in level_guide ] df_bpr = pd.DataFrame.from_dict(ball_pots_revs) df_bpr And the plot… import matplotlib.pyplot as plt from matplotlib.offsetbox import OffsetImage, AnnotationBbox def getImage(path, zoom=1): return OffsetImage(plt.imread(path), zoom=zoom) # Substitute with `df_bpr['potions_img']` or `df_bpr['revives_img']` paths = df_bpr['balls_img'] x = df_bpr['levels'] y = df_bpr['balls'] # Substitute with `df_bpr['potions']` or `df_bpr['revives']` fig, ax = plt.subplots() ax.scatter(x, y) for x0, y0, path in zip(x, y,paths): ab = AnnotationBbox(getImage(path), (x0, y0), frameon=False) ax.add_artist(ab) Yeah that's nice, now do potions. And revives! What?! I get only 20 max revives for every level I wonder how the game devs decide how many reward items to give for the levels. Maybe they did what my teacher used to grade our papers, hold the student essays, walk to the staircase, throw them down the stairs, the nearest get As, the furthest gets Fs. The game devs must have done the same to the reward items and all the max revives plushies drops the bottom of the stairs. Come to think of it, why ain't Pokemon Company churning out plushies for Pokemon items? I would love to mix a bunch of different berries plushie and put them in the fridge whenever we run out of fruits before I go to the store to replenish them. I guess I could generate them with “un- ” on for now… stable diffusion huggingface Reading the plots individual is tedious... Can't you put all the items together? paths = list(df_bpr['revives_img']) + list(df_bpr['potions_img']) + list(df_bpr['balls_img']) x = list(df_bpr['levels']) + list(df_bpr['levels']) + list(df_bpr['levels']) y = list(df_bpr['revives']) + list(df_bpr['potions']) + list(df_bpr['balls']) fig, ax = plt.subplots() ax.scatter(x, y) for x0, y0, path in zip(x, y,paths): ab = AnnotationBbox(getImage(path), (x0, y0), frameon=False) ax.add_artist(ab) That looks better! Hmmm, something smells off... 待って! (Wait a minute!) Did the images slapped on each other when they are on the same point?! Q: Can't you try to just group them together if they fall on the same point?! A: I guess so, but do you really need some "beautiful" infographics just to know whether it's worth it for you to grind up to level 50? Lets try to combine some images and group them together From https://stackoverflow.com/questions/30227466/combine-several-images-horizontally-with-python def combine_images(image_filenames, output_filename, pilmode='RGBA', how='v'): imgs = [Image.fromarray(imageio.imread(i, pilmode=pilmode)) for i in image_filenames] max_shape = sorted( [(np.sum(i.size), i.size) for i in imgs])[-1][-1] # Vertical if how[0].lower()=='v': imgs_comb = np.vstack([i.resize(max_shape) for i in imgs]) # Horizontal elif how[0].lower()=='h': imgs_comb = np.hstack([i.resize(max_shape) for i in imgs]) # Square elif how[0].lower()=='s': if len(image_filenames) % 2 != 0: # If odd, add a blank image. Image.new('RGBA', max_shape).save('blank.png') image_filenames += ['blank.png'] combine_images(image_filenames[:len(image_filenames)//2], 'part1.png', how='h') combine_images(image_filenames[len(image_filenames)//2:], 'part2.png', how='h') imgs = [Image.fromarray(imageio.imread('part1.png', pilmode=pilmode)), Image.fromarray(imageio.imread('part2.png', pilmode=pilmode))] scale = len(image_filenames[:len(image_filenames)//2]) max_shape =(max_shape[0]*scale, max_shape[1]*1) imgs_comb = np.vstack([i.resize(max_shape) for i in imgs]) imgs_comb = Image.fromarray(imgs_comb) imgs_comb.save(output_filename) Using the function combine_images list_im = ['max_revives.thumbnail.png', 'max_potions.thumbnail.png', 'ultra_balls.thumbnail.png', 'golden_razz_berries.thumbnail.png'] combine_images(list_im, output_filename='rewards-s.png', how='square') display(JupyterImage(filename='rewards-s.png')) Cool! Now do the plots again. We'll have to group the images together by (x,y) points instead of keeping them in a nice table. def fetch_num_items(level_guide_l, x): for r in level_guide_l['rewards']: if r.endswith(x): return level_guide_l['rewards'][r] return 0 ball_pots_revs_ber = [{'levels': l, 'potions': fetch_num_items(level_guide[l], '_potions'), 'revives': fetch_num_items(level_guide[l], '_revives'), 'balls': fetch_num_items(level_guide[l], '_balls'), 'berries': fetch_num_items(level_guide[l], '_berries'), } for l in level_guide ] df_bprb = pd.DataFrame.from_dict(ball_pots_revs_ber) from collections import defaultdict data_points = defaultdict(list) for idx, row in df_bprb.iterrows(): x = row['levels'] for i in row.index: if i != 'levels': y = row[i] if y == 0: continue data_points[(x,y)].append( next(r for r in level_guide[x]['rewards'] if r.endswith(i)) + '.thumbnail.png' ) print(data_points) [out]: defaultdict(list, {(41, 20): ['max_potions.thumbnail.png', 'max_revives.thumbnail.png', 'ultra_balls.thumbnail.png', 'razz_berries.thumbnail.png'], (42, 20): ['max_potions.thumbnail.png', 'max_revives.thumbnail.png', 'ultra_balls.thumbnail.png', 'nanab_berries.thumbnail.png'], ..., (50, 50): ['max_potions.thumbnail.png', 'ultra_balls.thumbnail.png'], (50, 20): ['max_revives.thumbnail.png']}) Now, we have the images we want to combine for every scatter point data_points_imgs = {} for k, v in data_points.items(): fname = f"{'-'.join(map(str, k))}.png" if len(v) in [1,2]: combine_images(v, output_filename=fname, how='horizontal') else: combine_images(v, output_filename=fname, how='square') data_points_imgs[k] = fname Go, go, go. Plot it! import matplotlib as mpl import matplotlib.pyplot as plt from matplotlib.offsetbox import OffsetImage, AnnotationBbox import seaborn as sns sns.set(rc={'figure.figsize':(11.7,8.27)}) x, y = zip(*data_points_imgs) fig, ax = plt.subplots() ax.scatter(x, y, s=0.01) plt.xlabel('Level') plt.ylabel('Rewards') plt.xlim([40, 51]) ppp = data_points_imgs.values() for x0, y0, path in zip(x, y,ppp): ab = AnnotationBbox( OffsetImage(plt.imread(path), zoom=1), (x0, y0), frameon=False) ax.add_artist(ab) Now, you see the exponential grind and rewards, whatcha gonna do? https://youtu.be/MpaHR-V_R-o?embedable=true Also published . here