Matplotlib Colormaps – Choosing a different color for each graph/line/subject
Asked Answered
H

4

5

I created a script that reads and plots .txt files and their content (numbers/values). Each .txt file is located in a different folder. Each folder, in turn, represents one subject from which the data stems.

This code works fine. Python reads each single .txt. file and plots 23 individual graphs/lines into one single plot. Python uses some standard colors here, i.e., each graph is automatically presented in a different color.

What I would like to do is the following: Instead of using the standard colors that are assigned by python automatically without adding any color related code, I would like to use a specific colormap (for example "plasma") from matplotlib.

The problem: no matter what code from the internet I use, all graphs/lines/subjects always receive the same color (e.g. the first color or last color from the plasma colormap).

How do I specify the code so that every line gets one distinct color from a colormap of choice?

Here is my code:

# Initialize
import numpy as np
import matplotlib.pyplot as plt
from scipy import signal
from matplotlib.pyplot import cm

# Numpy.loadtxt – Loads data from a textfile. Scipy.signal.welch – Creation of the FFT/power-spectrum. f, Pxx_den creates the ideal frequencies/FFT (f, Welch = Power Spectrum or Power Spectral Density)
Subjects = ["Subject1", "Subject2", "Subject3", "Subject4", "Subject5", "Subject7", "Subject8", "Subject9", "Subject10", "Subject11", "Subject12", "Subject13",
            "Subject14", "Subject15", "Subject16", "Subject17", "Subject18", "Subject19", "Subject20", "Subject22", "Subject23", "Subject24", "Subject25"]

for Subject in Subjects:

    Subject = np.loadtxt("/volumes/SanDisk2/fmri/dataset/processed/extracted_timeseriespython/restingstate/{0}/TimeSeries.SPC.Core_ROI.{0}.txt".format(Subject), comments="#", delimiter=None,
                         converters=None, skiprows=0, usecols=0, unpack=False, ndmin=0, encoding=None, max_rows=None, like=None)

    f, Welch = signal.welch(Subject, fs=1.0, window="hann", nperseg=None, noverlap=None, nfft=1024, detrend="constant", return_onesided=True, scaling="density", axis=-1, average="mean")

    cmap = plt.get_cmap("inferno")
    slicedCM = cmap(np.linspace(0, 1, len(Subjects)))

    plt.plot(f, Welch, c=slicedCM[Subjects.index(Subject)]) 
    

# Grid labels
plt.title("Power Spectrum for all subjects", fontsize=12, fontweight="bold")
plt.xlabel("Log Frequency [Hz]", fontsize=11, fontweight="bold")
plt.ylabel("Log Power [Hz]", fontsize=11, fontweight="bold")

# Grid dimenions and style
plt.xlim([0.005, 0.2]) # x-axis range
plt.ylim([0, 100]) # y-axis range

plt.xticks(np.arange(0, 0.21, 0.025)) # x ticks range (start, end, step)
plt.yticks(np.arange(0, 101, 10)) # y ticks range (start, end, step)

plt.grid(True) # Show grid
plt.rc("axes", axisbelow=True) # Grid behind figures
plt.rc("grid", linestyle="-", color="black") # Grid look

# Show result
plt.show()

Here is the resulting screenshot, showing that the standard colors are used instead of the desired plasma colormap:

screenshot

I'm running matplotlib 3.5.0 with MacOSX as backend.

Hashimoto answered 28/11, 2021 at 11:29 Comment(3)
Hi Johan, the results are 3.5.0 and MacOSX. I attached a screenshot to better explain the problem too. The screenshot shows the standard color settings used by python/matplotlib. What I need is that every line has one color, just as shown here, but from a specific colormap. abload.de/img/screenshot2021-11-28acnjbw.pngHashimoto
Could you edit your post and try to make your code easily reproducible, for example similar to my test code and plot?Wigeon
I dont understand why you are plotting the same thing 23 times. Maybe just plot it once.Amortization
F
4

One way to achieve your goal is to slice-up a colormap and then plot each line with one of the resulting colors. See the lines below that can be integrated in your code in appropriate places.

import numpy as np
import matplotlib.pyplot as plt

# 1. Choose your desired colormap
cmap = plt.get_cmap('plasma')

# 2. Segmenting the whole range (from 0 to 1) of the color map into multiple segments
slicedCM = cmap(np.linspace(0, 1, len(Subjects))) 

# 3. Color the i-th line with the i-th color, i.e. slicedCM[i]
plt.plot(f, Welch, c=slicedCM[Subjects.index(Subject)]) 

(The first two lines can be put in the beginning and you can replace the line plotting curves in your code with the third line of code suggested above.)

Alternatively, and perhaps a neater approach, is using the below lines inside your main loop through Subjects:

cmap = plt.get_cmap('inferno')
plt.plot(f, Welch, c=cmap(Subjects.index(Subject)/len(Subjects))) 

(I see in your question that you are changing Subject when you load the file again into Subject. Just use another variable name, say, data = np.loadtxt... and then f, Welch = signal.welch(data, ..... Keep the codes for plotting with different colors as suggested above and you won't have any problem.)

Foregoing answered 28/11, 2021 at 17:48 Comment(11)
Your code works fine. But the result is the same as with all other codes that I tried: only 1 color from the colormap is used for all graphs. More precisely, when I use the value 23 in "for i in range(23):", the last color of the colormap plasma is used (yellow). When I change this value to 1, the first value in the colormap is used (dark purple). I feel like Python treats all subjects/graphs/lines as one object once they are loaded via the "Subject=np.loadtxt("/volumes/San..." or "f, Welch=signal.welch(Subject, fs=1.0 ..." lines. Could the problem be located here? I am still totally new to P.Hashimoto
Ok, I guess only now I'm starting to understand the real problem. It seems like "f" and "Welch" 2d arrays or matrices or lists of lists or .... so you are plotting multiple lines for each "i", and you want them to have different colors, is that right?Foregoing
Hello and thanks for helping. Each line derives from a distinct .txt file. Python understands that since it colours every line in a different color by default (without adding any color related code at all). Once we add a colorcode, e.g., using a colormap, Python somehow applies the same color for all lines. This is the problem. For example, the code shown above by Johan works fine. It is not a bug or problem due to my Python version or computer, but the problem is related to the code. We need to tell Python to use a new color for each line but from a specific colormap.Hashimoto
Sorry, but I'm still a bit confused. You are plotting the same thing 23 times in your code. Would you check if that's the problem? I edited my answer to fix what can be the issue; check and let me know if that helps. I separately plotted each curve stored in 'f' and 'Welch' and color-coded them.Foregoing
I don't plot the same thing 23 times. I plot 23 graphs based on 23 subjects. Each subject is a different txt file with different numeric values. Thats why in the screenshot from me shown above every graph has a different color and shape. If I would plot the same graph 23 times, the plot would only show 1 single line in 1 color instead of 23 lines in 23 colors. For the final line of your updated code ("plt.plot(f[i, :], Welch[i, :], c=slicedCM[i])") I receive the following error: "IndexError: too many indices for array: array is 1-dimensional, but 2 were indexed". I will try adapting your code.Hashimoto
Thanks for telling me the error. So my guess was wrong about f/Welsch being 2D. Please check the edited code and this should color-code the "Subjects", which is hopefully what you want. (On the repetition comment) Well ...., the 23-iteration loop is doing the same thing over and over again, but you don't see them since they are plotted on top of each other.Foregoing
Thanks for your patience with me. Probably we are close to solve the problem now. I used your code and receive the following error for the last plt.plot line: "plt.plot(f, Welch, c=slicedCM[Subjects.index(Subject)]) ValueError: array([-3.15046e-01, -6.41514e-02, -9.21697e-01, -9.76516e-01, -5.46322e-01, -8.57073e-01, -1.59524e+00, -4.47158e-01, ....". Two questions: 1. Do I understand you correctly that my for loop results in plotting each of the 23 lines 23 times (on each other, respectively)? 2. Is the error above a result of my faulty for loop?Hashimoto
You really should not see that error. But let's see. Would you please try and plot with the color set as "cmap(Subjects.index(Subject)/len(Subjects))"; I'm adding this to my answer. Re your questions, 1. Yes you got it. 2. As far as I can see, NO. That won't cause that error but it is just redundant, and you can lose the loop and plot one curve for each "subject" (again, as far as I can tell).Foregoing
I updated my script in the initial post. Please take a look. I tried both of your last suggestions and they lead to the same error for the plt.plot line: "plt.plot(f, Welch, c=slicedCM[Subjects.index(Subject)]) ValueError: array([-3.15046e-01, -6.41514e-02, ..., -1.57027e+00]) is not in list". Sorry, I know that it is crazy.Hashimoto
Oh wow, how did I miss this?! You are changing "Subject" when you load the file again into "Subject". Just use another variable name, say, "data = np.loadtxt....." and then "f, Welch = signal.welch(data, ....". Keep the rest of the code as it is and you won't see the error.Foregoing
Thank you so much for all your help, Homayoun! It finally works! And thanks to everyone else in here as well for providing help!Hashimoto
P
8

enter image description here

You want to give to the colormap instance an argument varying between 0 and 1, this requires a minimum of planning, e.g.,

x = np.linspace(0,355/113, 101)
p = plt.get_cmap('plasma')
n = 23
for i in range(n):
    plt.plot(x, np.sin(x-i/2/n), color=p(i/(n-1)))
Punishment answered 29/11, 2021 at 11:39 Comment(1)
I am tempted to delete my answer, but I sort of like the colorful arch that I'd got...Punishment
F
4

One way to achieve your goal is to slice-up a colormap and then plot each line with one of the resulting colors. See the lines below that can be integrated in your code in appropriate places.

import numpy as np
import matplotlib.pyplot as plt

# 1. Choose your desired colormap
cmap = plt.get_cmap('plasma')

# 2. Segmenting the whole range (from 0 to 1) of the color map into multiple segments
slicedCM = cmap(np.linspace(0, 1, len(Subjects))) 

# 3. Color the i-th line with the i-th color, i.e. slicedCM[i]
plt.plot(f, Welch, c=slicedCM[Subjects.index(Subject)]) 

(The first two lines can be put in the beginning and you can replace the line plotting curves in your code with the third line of code suggested above.)

Alternatively, and perhaps a neater approach, is using the below lines inside your main loop through Subjects:

cmap = plt.get_cmap('inferno')
plt.plot(f, Welch, c=cmap(Subjects.index(Subject)/len(Subjects))) 

(I see in your question that you are changing Subject when you load the file again into Subject. Just use another variable name, say, data = np.loadtxt... and then f, Welch = signal.welch(data, ..... Keep the codes for plotting with different colors as suggested above and you won't have any problem.)

Foregoing answered 28/11, 2021 at 17:48 Comment(11)
Your code works fine. But the result is the same as with all other codes that I tried: only 1 color from the colormap is used for all graphs. More precisely, when I use the value 23 in "for i in range(23):", the last color of the colormap plasma is used (yellow). When I change this value to 1, the first value in the colormap is used (dark purple). I feel like Python treats all subjects/graphs/lines as one object once they are loaded via the "Subject=np.loadtxt("/volumes/San..." or "f, Welch=signal.welch(Subject, fs=1.0 ..." lines. Could the problem be located here? I am still totally new to P.Hashimoto
Ok, I guess only now I'm starting to understand the real problem. It seems like "f" and "Welch" 2d arrays or matrices or lists of lists or .... so you are plotting multiple lines for each "i", and you want them to have different colors, is that right?Foregoing
Hello and thanks for helping. Each line derives from a distinct .txt file. Python understands that since it colours every line in a different color by default (without adding any color related code at all). Once we add a colorcode, e.g., using a colormap, Python somehow applies the same color for all lines. This is the problem. For example, the code shown above by Johan works fine. It is not a bug or problem due to my Python version or computer, but the problem is related to the code. We need to tell Python to use a new color for each line but from a specific colormap.Hashimoto
Sorry, but I'm still a bit confused. You are plotting the same thing 23 times in your code. Would you check if that's the problem? I edited my answer to fix what can be the issue; check and let me know if that helps. I separately plotted each curve stored in 'f' and 'Welch' and color-coded them.Foregoing
I don't plot the same thing 23 times. I plot 23 graphs based on 23 subjects. Each subject is a different txt file with different numeric values. Thats why in the screenshot from me shown above every graph has a different color and shape. If I would plot the same graph 23 times, the plot would only show 1 single line in 1 color instead of 23 lines in 23 colors. For the final line of your updated code ("plt.plot(f[i, :], Welch[i, :], c=slicedCM[i])") I receive the following error: "IndexError: too many indices for array: array is 1-dimensional, but 2 were indexed". I will try adapting your code.Hashimoto
Thanks for telling me the error. So my guess was wrong about f/Welsch being 2D. Please check the edited code and this should color-code the "Subjects", which is hopefully what you want. (On the repetition comment) Well ...., the 23-iteration loop is doing the same thing over and over again, but you don't see them since they are plotted on top of each other.Foregoing
Thanks for your patience with me. Probably we are close to solve the problem now. I used your code and receive the following error for the last plt.plot line: "plt.plot(f, Welch, c=slicedCM[Subjects.index(Subject)]) ValueError: array([-3.15046e-01, -6.41514e-02, -9.21697e-01, -9.76516e-01, -5.46322e-01, -8.57073e-01, -1.59524e+00, -4.47158e-01, ....". Two questions: 1. Do I understand you correctly that my for loop results in plotting each of the 23 lines 23 times (on each other, respectively)? 2. Is the error above a result of my faulty for loop?Hashimoto
You really should not see that error. But let's see. Would you please try and plot with the color set as "cmap(Subjects.index(Subject)/len(Subjects))"; I'm adding this to my answer. Re your questions, 1. Yes you got it. 2. As far as I can see, NO. That won't cause that error but it is just redundant, and you can lose the loop and plot one curve for each "subject" (again, as far as I can tell).Foregoing
I updated my script in the initial post. Please take a look. I tried both of your last suggestions and they lead to the same error for the plt.plot line: "plt.plot(f, Welch, c=slicedCM[Subjects.index(Subject)]) ValueError: array([-3.15046e-01, -6.41514e-02, ..., -1.57027e+00]) is not in list". Sorry, I know that it is crazy.Hashimoto
Oh wow, how did I miss this?! You are changing "Subject" when you load the file again into "Subject". Just use another variable name, say, "data = np.loadtxt....." and then "f, Welch = signal.welch(data, ....". Keep the rest of the code as it is and you won't see the error.Foregoing
Thank you so much for all your help, Homayoun! It finally works! And thanks to everyone else in here as well for providing help!Hashimoto
B
1

My CMasher has the function take_cmap_colors() (https://cmasher.readthedocs.io/user/usage.html#taking-colormap-colors), which was written to handle exactly this case. Simply provide it with a colormap, the number of colors and optionally a range that you want, and it will return you with a list of colors (in a format that MPL understands) taken uniformly from the provided colormap.

So, in your particular case, you could do this with:

# Import CMasher
import cmasher as cmr

# Obtain colors from a colormap
colors = cmr.take_cmap_colors('inferno', len(Subjects))

# YOUR FOR-LOOP
for Subject, color in zip(Subjects, colors):

    Subject = np.loadtxt("/volumes/SanDisk2/fmri/dataset/processed/extracted_timeseriespython/restingstate/{0}/TimeSeries.SPC.Core_ROI.{0}.txt".format(Subject), comments="#", delimiter=None,
                         converters=None, skiprows=0, usecols=0, unpack=False, ndmin=0, encoding=None, max_rows=None, like=None)

    f, Welch = signal.welch(Subject, fs=1.0, window="hann", nperseg=None, noverlap=None, nfft=1024, detrend="constant", return_onesided=True, scaling="density", axis=-1, average="mean")

    plt.plot(f, Welch, c=color)

You can provide the function with the name of any registered colormap in MPL, or with a Colormap object if you use custom, non-registered colormaps.

Also take a look at the colormaps that CMasher provides to you as well (https://cmasher.readthedocs.io/user/introduction.html). You might find a better candidate for your plot in there.

Bacitracin answered 30/11, 2021 at 10:40 Comment(0)
P
0

enter image description here

If you want to color different Artists (e.g., lines, arcs, rectangles etc) according to a color scheme based on a colormap, then

  1. A Colormap object, the one you get like in cmap = plt.get_cmap('rainbow') is a callable,
  2. It maps real numbers comprised between 0 and 1 to colors from the colormap.

Consequently, you will associate each one of the artists you want to color with a real number 0 ≤ x ≤ 1 and, when you instantiate the artist, use the color=cmap(x) keyword argument.

In the common case that you have to color a sequence of N objects, to fully use the colormap range of colors the recipe is

for n in range(N):
    plt.plot(x, y[n], color=cmap(n/(N-1)))

Eventually, here it's my rainbow code

import matplotlib.pyplot as plt
from matplotlib.patches import Arc, Rectangle
import numpy as np

fig, ax = plt.subplots()
plt.axis('off')

# the "panorama"
ax.add_patch(Rectangle((0,0), 1, 0.7, color='skyblue'))
ax.fill_between(x, 0.2+0.05*x+0.1*np.sin((6-2*x)*x-0.6)**2, color="#206030")

# no ax.plot(…), my artist of choice are Arc's 
# https://matplotlib.org/stable/api/_as_gen/matplotlib.patches.Arc.html
cmap = plt.get_cmap('rainbow')
for (n, dr) in enumerate(np.linspace(0, 0.1, 256)):
    arc = Arc((+0.5, -0.1), 1.4+dr, 1.4+dr,  theta1=0, theta2=180,
               color=cmap(n/256), alpha=0.9)
    ax.add_patch(arc)

# tweak the x and y limits, fix the aspect ratio and have circular circles
ax.set_xlim((0,1)), ax.set_ylim((0,0.7)), ax.set_aspect(1)
Punishment answered 3/7, 2024 at 9:46 Comment(0)

© 2022 - 2025 — McMap. All rights reserved.