Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

Custom color mapping circos nodes and edges #689

Merged

Conversation

zktuong
Copy link
Collaborator

@zktuong zktuong commented Jun 16, 2022

This PR resolves issue #682 and #572.

PR Checklist

Please ensure that you have done the following:

  1. PR in from a fork off your branch. Do not PR from <your_username>:master, but rather from <your_username>:<branch_name>.
  2. If you're not on the contributors list, add yourself to AUTHORS.rst.

Code Changes

If you are adding code changes, please ensure the following:

  • Ensure that you have added tests.
  • Run all tests ($ pytest .) locally on your machine.
    • Check to ensure that test coverage covers the lines of code that you have added.
    • Ensure that all tests pass.

Documentation Changes

If you are adding documentation changes, please ensure the following:

  • Build the docs locally.
  • View the docs to check that it renders correctly.

PR Description

Please describe the changes proposed in the pull request:

Hi, first just want to express my gratitude for making this fantastic package!

In this PR, I've just simply added node_palette and edge_palette kwargs with the main idea to allow circos to accept either a list or dictionary of custom color palettes for colouring categories. annotate.node_colormapping and annotate.edge_colormapping also received a similar treatment but accepts palette as the kwarg.

This gets around the issue of only limited to 12 colours and allow for custom color selection. I've inserted the kwargs into the functions in .api but I've only tried this on circos so i'm not sure if it impacts on the rest of the package.

Minimal example to illustrate the outcome:

import networkx as nx
import nxviz as nv
import matplotlib.pyplot as plt
from itertools import cycle
from nxviz import annotate

# 14 categories
categories = [
    "sun",
    "moon",
    "stars",
    "cloud",
    "wheel",
    "box",
    "plant",
    "chair",
    "slippers",
    "tablet",
    "laptop",
    "dishwasher",
    "bicycle",
    "piano",
    "laptop",
]

# 20 colors - providing an uneven list on purpose
palette = [
    "#1f77b4",
    "#ff7f0e",
    "#279e68",
    "#d62728",
    "#aa40fc",
    "#8c564b",
    "#e377c2",
    "#b5bd61",
    "#17becf",
    "#aec7e8",
    "#ffbb78",
    "#98df8a",
    "#ff9896",
    "#c5b0d5",
    "#c49c94",
    "#f7b6d2",
    "#dbdb8d",
    "#9edae5",
    "#ad494a",
    "#8c6d31",
]

categorical = cycle(categories[0:4]) # max 4 distinct categories
categories[0:4]
# ['sun', 'moon', 'stars', 'cloud']
many_categorical = cycle(categories) # up to 14

n = 71
p = 0.01
G = nx.erdos_renyi_graph(n=n, p=p)

for n in G.nodes():
    G.nodes[n]["group1"] = next(categorical)
    G.nodes[n]["group2"] = next(many_categorical) # up to 14
for u, v in G.edges():
    G.edges[u, v]["edge_group1"] = next(categorical)
    G.edges[u, v]["edge_group2"] = next(many_categorical) # up to 14
    G.edges[u, v]["thickness"] = 3  # just to be able see the edge colours later

Current default behavior as it is right now (before/after this PR) i.e. don't specify the palette options:

nv.circos(G, group_by="group1", node_color_by="group1")
annotate.node_colormapping(G, color_by="group1")

image

when there's >12 categories, just modified the error message to ask for people to provide their own palette.

nv.circos(G, group_by="group2", node_color_by="group2")

image

this PR's proposed changes:

nv.circos(G, group_by="group1", node_color_by="group1", node_palette=palette[:4]) # specify 4 colors for 4 groups

image

now with more than 12 categories (14), and a long color palette (20 colors)

nv.circos(G, group_by="group2", node_color_by="group2", node_palette=palette)

image

same as above but limit to 7 colors - colors start to cycle if palette is provided as a list.

nv.circos(G, group_by="group2", node_color_by="group2", node_palette=palette[:7])

image

if provided as a dictionary:

pal = {'moon':'red', 'stars':'yellow', 'sun':'black', 'cloud':'blue'}
nv.circos(G, group_by="group1", node_color_by="group1", node_palette=pal)
annotate.node_colormapping(G, color_by="group1", palette=pal)

image

order of keys don't matter

pal = {'moon':'red', 'cloud':'pink', 'stars':'yellow', 'sun':'black'}
nv.circos(G, group_by="group1", node_color_by="group1", node_palette=pal)
annotate.node_colormapping(G, color_by="group1", palette=pal)

image

can mix colors/hex codes

pal = ['pink', '#1f77B4', 'green', '#ff7f0e']
nv.circos(G, group_by="group1", node_color_by="group1", node_palette=pal)
annotate.node_colormapping(G, color_by="group1", palette=pal)

image

swapping of order of colors in a list matters. But the plot should reflect this correctly - if you look up at the dictionary examples, the same order is preserved.

pal = ['pink', '#1f77B4', '#ff7f0e', 'green'] # swapped the order of the last two colours
nv.circos(G, group_by="group1", node_color_by="group1", node_palette=pal)
annotate.node_colormapping(G, color_by="group1", palette=pal)

image

Can be used on edges as well:

nv.circos(G, 
          group_by="group2", 
          node_color_by="group2", 
          edge_color_by = 'edge_group1', 
          node_palette=palette, 
          edge_palette = palette,
          edge_lw_by = 'thickness',
         )
annotate.node_colormapping(G, color_by="group2", palette=palette)
annotate.edge_colormapping(G, color_by="edge_group1", palette=palette)

image

As a sanity check, just to ensure that the color/order is respected you can see in this small graph where only 2 categories will be repeated twice (sun and moon), the color mapping remains consistent:

default i.e. palette not provided

n = 6
p = .01
G = nx.erdos_renyi_graph(n=n, p=p)
assignments = []
for n in G.nodes():
    G.nodes[n]["group1"] = next(categorical)
    assignments.append(G.nodes[n]["group1"])
    G.nodes[n]["group2"] = next(many_categorical)
assignments
# ['sun', 'moon', 'stars', 'cloud', 'sun', 'moon']
nv.circos(G, group_by="group1", node_color_by="group1")
annotate.node_colormapping(G, color_by="group1")

image

palette provided as a list. Because sun was added first (it was added sequentially as 'sun', 'moon', 'stars', 'cloud', 'sun', 'moon'), it should be pink. cloud is the last unique entry, so should be green.

pal = ['pink', '#1f77B4', '#ff7f0e', 'green']
nv.circos(G, group_by="group1", node_color_by="group1", node_palette=pal)
annotate.node_colormapping(G, color_by="group1", palette=pal)

image

palette is provided as a dictionary.

pal = {'moon':'red', 'stars':'yellow', 'sun':'black', 'cloud':'blue'}
nv.circos(G, group_by="group1", node_color_by="group1", node_palette=pal)
annotate.node_colormapping(G, color_by="group1", palette=pal)

image

let me know what you think!

Cheers,
Kelvin

Relevant Reviewers

Please tag maintainers to review.

Copy link
Owner

@ericmjl ericmjl left a comment

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

@zktuong firstly, I want to massively apologize for neglecting this PR (and the repo more generally). Work (Moderna) and life (kids) caught up with me and I haven't had as much success in maintaining older projects, but I'm very grateful that you did this PR.

On reviewing it, I'm very impressed by the careful work that you did here! Thank you so much for putting in the time and effort. I am going to merge the PR right away and cut a new release.

I would also like to invite you to be a co-maintainer of the package. Is that something that you'd be amenable to? I will give you collaborator permissions on the repo straight away, and I do hope you will accept it, but if you prefer not to, I will respect your decision too!

@ericmjl ericmjl merged commit 1bbbcda into ericmjl:master Aug 9, 2024
@zktuong
Copy link
Collaborator Author

zktuong commented Aug 11, 2024

Thank you! Not sure how much i can contribute but will try!

@zktuong zktuong deleted the custom_color_mapping_circos_nodes_and_edges branch September 3, 2024 03:02
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment
Labels
None yet
Projects
None yet
Development

Successfully merging this pull request may close these issues.

2 participants