docs: def kegg_pathway(df, pathway, a, b=None, ids_from="Proteins", cmap=cm.PuOr_r, is_log2=False, fillna=None, z_score=1):
      """
      Visualize data on a kegg pathway.
  
  
      :param df:
      :param pathway:
      :param a:
      :param b:
      :param ids_from:
      :param cmap:
      :param is_log2:
      :param fillna:
      :param z_score:
      :return:
      """
  
      df = df.copy()
  
      if np.any(df.values < 0) and not is_log2:
          warnings.warn("Input data has negative values. If data is log2 transformed, set is_log2=True.")
  
      if fillna is not None:
          df = df.fillna(fillna)
  
      if z_score is None:
          pass
      elif z_score == 0:
          df = (df - df.median(axis=0)) / df.std(axis=0)
      elif z_score == 1:
          df = ((df.T - df.median(axis=1).T) / df.std(axis=1).T).T
  
  
      if b is not None:
          # Calculate ratio between two groups
          g1, g2 = df[a].values, df[b].values
  
          if is_log2:
              dr = np.nanmean(g2, axis=1) - np.nanmean(g1, axis=1)
          else:
              dr = np.log2(np.nanmean(g2, axis=1) / np.nanmean(g1, axis=1))
  
      else:
          g1 = df[a].values
          dr = np.nanmean(g1, axis=1)
  
  
      maxi = np.max(abs(dr))
      norm = mpl.colors.Normalize(vmin=-maxi, vmax=maxi)
      mapper = cm.ScalarMappable(norm=norm, cmap=cm.PuOr_r) # Orange up
  
      node_colors = {}
      for p, v in zip(df.index.get_level_values(ids_from), dr):
          pid = str(p).split(";")[-1]
  
          if "_" in pid:
              pid = pid[:pid.index("_")]
  
          node_colors[pid] = mpl.colors.rgb2hex(mapper.to_rgba(v))
  
  
      global uniprot_kegg_cache
  
      # Only do this once
      upids = list( node_colors.keys() )
      upids = [p for p in upids if p not in uniprot_kegg_cache.keys()]
  
      if upids:
          new_pairs = get_uniprot_id_mapping_pairs('ACC+ID', 'KEGG_ID', upids)
          uniprot_kegg_cache.update(new_pairs)
  
          for p in upids:
              if p not in uniprot_kegg_cache:
                  uniprot_kegg_cache[p] = None # Not found, don't look again
  
  
      with StringIO() as f:
          f.write('#hsa\tData\n')
          for k, c in list(node_colors.items()):
              if k in uniprot_kegg_cache and uniprot_kegg_cache[k] is not None:
                  kids = uniprot_kegg_cache[k]
                  for kegg_id in kids:
                      f.write('%s\t%s\n' % (kegg_id.split(':')[-1], c ))
  
          # Reset file
          f.seek(0)
  
          url = 'https://www.kegg.jp/kegg-bin/mcolor_pathway'
          m = MultipartEncoder(
              fields={
                  'map': pathway,
                  'mapping_list': ('filename', f),
                  'mode': 'color',
                  'submit': 'Exec',
                  'reference': 'white',
                   }
          )
  
      r = requests.post(url, data=m, headers={'Content-Type': m.content_type})
      if r.status_code == 200:
          # src="/tmp/mark_pathway154353327948969/hsa04010.1.png"
          ms = re.finditer('src="(/tmp/mark_pathway[^"]*.png)"', r.text)
          m = list(ms).pop()
  
          # Download image data
          image = Image.open(requests.get('http://www.kegg.jp%s' % m.group(1), stream=True).raw)
          width, height = image.size   # Get dimensions
          image = image.crop((1, 1, width-1, height-1)) # Crop black outline
          print("Scale range: %.2f .. %.2f" % (-maxi, maxi))
  
          return image