简体   繁体   English

根据轴改变颜色,matplotlib

[英]change color according to the axis, matplotlib

图形

In this graph I would like to make everything negative in x to be red, and in y to be green, or just the 3 most negative dots be red and 3 most positive dots be green.在此图中,我想让 x 中的所有负值都为红色,y 中的所有负值都为绿色,或者只有 3 个最负的点为红色,3 个最正的点为绿色。

from matplotlib import pyplot as plt

ex_x = [res1,res2,res3,res4,res5,res6,res7,res8,res9,res10,res11,res12,res13,res14,res15,res16,res17,res18,res19,res20,res21,res22,res23,res24,res25,res26,res27,res28,res29,res30,res31,res32,res33,res34,res35,res36,res37,res38,res39,res40,res41,res42,res43,res44,res45,res46,res47]
ex_y = ["1","2","2.1","2.1.1","2.2","3","3.1","3.2","4","4.1","4.1.1","4.1.1A)","4.1.1B)","4.1.2","4.2","4.2A)","4.2B)","4.3","4.4","5","5.1","5.1.1","5.1.2","5.1.2A)","5.1.2B)","5.1.3","5.1.4","5.2","5.2.1","5.2.1A)","5.2.1B)","5.2.1C)","5.2.2","5.2.3","5.2.3A)","5.2.3B)","5.2.4","5.2.5","6","6.1","7","7.1","7.2","8.1","8.1A)","8.1B)","8.2"]

l_x = [0, 0]
l_y = [47, 0]

z_x = [-10, 10]
z_y = [0, 0]

plt.grid(True)

for x in range(-10,10):
    plt.plot(l_x, l_y, color = "k", linewidth = 2)

plt.plot(z_x, z_y, color = 'k', linewidth = 1)
plt.plot(ex_x, ex_y, marker='.')
plt.gca().invert_yaxis()
plt.title("Perfil Neuropsicológico")
plt.tight_layout()
plt.show()

I would recommend to transform the lists ex_x and ex_y to numpy arrays as a first step:作为第一步,我建议将列表 ex_x 和 ex_y 转换为 numpy arrays:

import numpy as np
ex_x = np.array(ex_x)
ex_y = np.array(ex_y)

As a second step, the selection of only positive or negative values is pretty straightforward:第二步,仅选择正值或负值非常简单:

plt.plot(ex_x[ex_x<0], ex_y[ex_x<0], color='red', marker='.', linestyle='none')
plt.plot(ex_x[ex_x>=0], ex_y[ex_x>=0], color='green', marker='.', linestyle='none')

This is one of the perks of numpy arrays!这是 numpy 数组的好处之一!

As you see, I have removed the connecting lines in this example.如您所见,我已删除此示例中的连接线。 Having those would be more involved (what would the color of a line between a red and a green dot be?).拥有这些会更加复杂(红色和绿色点之间的线的颜色是什么?)。 What you could do, of course, is plot an additional line with all the data in a neutral color, eg当然,你可以做的是 plot 一个额外的行,所有数据都以中性颜色显示,例如

plt.plot(ex_x, ex_y, color='dimgrey')

It would be easiest, then, to keep this line in the background by plotting it "before" the other points.那么,最简单的方法是将这条线绘制在其他点“之前”,以将其保留在背景中。


On a side note: I think in the for loop, you plot the same line 20 times.旁注:我认为在for循环中,您 plot 同一行 20 次。

Some ideas:一些想法:

import pylab as plt
import numpy as np
# make some test data (np.ndarray types)
y = np.random.random(100)-0.5
x = np.arange(len(y))

plt.plot( x, y)
plt.plot( x[y<0], y[y<0], 'r.') # color negative red
plt.plot( x[y>=0], y[y>=0], 'g.')
order = np.argsort(y)  # order of y sorted from lowest to highest
plt.plot( x[order[:3]], y[order[:3]], 'rs', ms=10, mfc='none')  # lowest 3
plt.plot( x[order[-3:]], y[order[-3:]], 'gs', ms=10, mfc='none')  # highest 3
plt.xlabel("x")
plt.ylabel("y")
plt.show()

样本图像

The answers already here are great - I just want to add a method to have the lines (not just the points) red or green depending on +x or -x side.这里已经有了很好的答案——我只想添加一种方法,根据+x-x侧使线条(而不仅仅是点)变为红色或绿色。

Unfortunately, there are no easy methods to just change the color of a line midpoint in matplotlib, the trick is to 'break up' the line so you can color each segment.不幸的是,没有简单的方法可以改变 matplotlib 中线中点的颜色,诀窍是“分解”这条线,这样你就可以为每个线段着色。 In your case, the trick is to add some 'fake' points at x = 0 so you can color the line -x to x = 0 red and the line x = 0 to +x in green.在您的情况下,诀窍是在x = 0处添加一些“假”点,这样您就可以将-xx = 0线涂成红色,将x = 0+x线涂成绿色。 A quick illustration of the 'fake' point and the line segmentation that we want to accomplish:我们想要完成的“假”点和线分割的快速说明:

快速说明

Here is a stab at it:这是一个尝试:

import numpy as np
import matplotlib.pyplot as plt
from matplotlib.collections import LineCollection


if __name__ == '__main__':
    ex_y = ["1", "2", "2.1", "2.1.1", "2.2", "3", "3.1", "3.2", "4", "4.1", "4.1.1", "4.1.1A)", "4.1.1B)", "4.1.2",
            "4.2", "4.2A)", "4.2B)", "4.3", "4.4", "5", "5.1", "5.1.1", "5.1.2", "5.1.2A)", "5.1.2B)", "5.1.3", "5.1.4",
            "5.2", "5.2.1", "5.2.1A)", "5.2.1B)", "5.2.1C)", "5.2.2", "5.2.3", "5.2.3A)", "5.2.3B)", "5.2.4", "5.2.5",
            "6", "6.1", "7", "7.1", "7.2", "8.1", "8.1A)", "8.1B)", "8.2"]

    # Make some fake data
    # Fixing random state for reproducibility
    np.random.seed(19680801)
    # Random data arrays the size of ex_y
    ex_x = list(np.random.randn(len(ex_y)))
    # Make a y-axis based on numbers so we can manipulate it later (ex_y we can use to set the y-tick labels later)
    numerical_y = list(np.arange(0, len(ex_y), 1))

    l_x = [0, 0]
    l_y = [47, 0]

    z_x = [-10, 10]
    z_y = [0, 0]

Now that we have all the set-up done, we can start looking into making the 'fake' points.现在我们已经完成了所有设置,我们可以开始研究制作“假”点。 The trick is to A) find between what points it goes from -x to +x and insert a fake point, and B) find the y-value of the fake point at x = 0 (aka the y-intercept).诀窍是 A) 在从-x+x的点之间找到并插入一个假点,和 B) 在x = 0处找到假点的 y 值(也称为 y 截距)。

Let's start with B. We need the 'x' and 'y' value of our 'fake' point - we already have 'x' (x=0) but we need the 'y' value (the y-intercept).让我们从 B 开始。我们需要“假”点的“x”和“y”值——我们已经有了“x”(x=0),但我们需要“y”值(y 截距)。 We can use the equation of a line ( y = m*x + c where m is the slope and c is a constant) to find the y-intercept.我们可以使用直线方程( y = m*x + c ,其中m是斜率, c是常数)找到 y 轴截距。 We can define a function such as find_y_intercept to find the y value between two points:我们可以定义一个function比如find_y_intercept来求两点之间的y值:

def find_y_intercept(point_1, point_2) -> float:
    """
    Find y-intercept
    :param point_1: (x1, y1)
    :param point_2: (x2, y2)
    :return:
    """
    x1, y1 = point_1
    x2, y2 = point_2

    # Equation of a line is y = m * x + c, where m is the slope, c is a constant, and x/y are the variables.
    # First, find slope m using x and y values of points 1 and 2
    m = (y2 - y1)/(x2 - x1)
    # Next, find constant c using one point (in this case point 1) and the slope m we just calculated, plug m and the point into the line equation
    c = y1 - m * x1
    # Now, plug x = 0 into the line equation to find the y intercept
    y_intercept = m * 0 + c
    return y_intercept

Now we have a method to calculate what value y will be when x = 0 , so we have the x (x=0) and y (y-intercept) values of our 'fake' point.现在我们有一种方法可以计算当x = 0时 y 的值,所以我们有“假”点的 x (x=0) 和 y(y 截距)值。

The next step is to find between what points it goes from -x to +x , and insert the 'fake' point in between those key points.下一步是找到它从-x+x的点之间,并在这些关键点之间插入“假”点。 To accomplish this we can compare each point in ex_x to the previous point and see if the sign changed - if it did, then add the new 'fake' point with x=0 and the y-intercept value using find_y_intercept we just defined.为了实现这一点,我们可以将ex_x中的每个点与前一个点进行比较,看看符号是否改变了——如果改变了,然后使用我们刚刚定义的find_y_intercept添加 x=0 和 y 截距值的新“假”点。

    for idx_point, x_point in enumerate(ex_x):
        # Skip the first value as we need 2 points for a line
        if idx_point == 0:
            continue
        # Get the x and y value of the point before so we can compare it with the current point to see if it crossed the x-axis
        x_point_before = ex_x[idx_point-1]
        y_point = numerical_y[idx_point]
        y_point_before = numerical_y[idx_point-1]
        # We are looking for when x goes from positive to negative and vice-versa
        if ((x_point < 0) == (x_point_before < 0)) == False:
            # If they are 0.0, we already introduced the 'fake' point so we can skip
            if x_point_before == 0.0 or x_point == 0.0:
                continue
            # The point before
            point_1 = (x_point_before, y_point_before)
            # The current point
            point_2 = (x_point, y_point)
            # Find y value
            y_intercept = find_y_intercept(point_1, point_2)
            # Insert new fake x and y points into our data
            numerical_y.insert(idx_point, y_intercept)
            ex_x.insert(idx_point, 0.0)

Okay, now we have inserted the 'fake' points into our x and y data arrays. We are almost done!好的,现在我们已经将“假”点插入到我们的 x 和 y 数据 arrays 中。我们几乎完成了!

The next step is to put all the lines (between each point in the data) in a matplotlib LineCollection .下一步是将所有线(数据中每个点之间的线)放入 matplotlib LineCollection 中 We could have a for loop plotting each line and writing some logic to color the line red or green depending on the x-values of the points.我们可以有一个for循环绘制每条线并编写一些逻辑根据点的 x 值将线着色为红色或绿色。 However, LineCollection is more efficient than plotting each line (this is key in large datasets).但是, LineCollection比绘制每条线更有效(这是大型数据集的关键)。 So to make a LineCollection we need to create segments (loosely based on this LineCollection example ) and color each segment accordingly.因此,要制作LineCollection ,我们需要创建线段(大致基于此 LineCollection 示例)并相应地为每个线段着色。

    # Create a set of line segments so that we can color them individually
    # This creates the points as a N x 1 x 2 array so that we can stack points
    # together easily to get the segments. The segments array for line collection
    # needs to be numlines x points per line x 2 (x and y)
    points = np.array([ex_x, numerical_y]).T.reshape(-1, 1, 2)
    segments = np.concatenate([points[:-1], points[1:]], axis=1)

    # What color each line segment will be
    linecolors = []
    for line in segments:
        # Check what the combined x value of endpoints of the line would be
        value = line[0][0] + line[1][0]
        # if both endpoints are negative, then that line is red
        color_line = 'r' if value < 0 else 'g'
        linecolors.append(color_line)

    # Create the line collection object, setting the colormapping parameters.
    # Have to set the actual values used for colormapping separately.
    lc = LineCollection(segments, colors=linecolors)
    lc.set_array(ex_x)

And now we can plot our LineCollection :现在我们可以 plot 我们的LineCollection

    fig, ax = plt.subplots()
    ax.add_collection(lc)
    ax.set_yticks(np.arange(0, len(ex_y), 1))
    ax.set_yticklabels(ex_y)
    for x in range(-10, 10):
        plt.plot(l_x, l_y, color="k", linewidth=2)
    plt.plot(z_x, z_y, color='k', linewidth=1)
    plt.gca().invert_yaxis()
    plt.title("Perfil Neuropsicológico")
    plt.tight_layout()
    plt.grid()
    plt.show()

最终无花果

Hope this helps.希望这可以帮助。 Cheers!干杯!

Here is the code, unbroken, so you can have a working copy:这是完整的代码,因此您可以获得一份工作副本:

import numpy as np
import matplotlib.pyplot as plt
from matplotlib.collections import LineCollection


def find_y_intercept(point_1, point_2) -> float:
    """
    Find y-intercept
    :param point_1: (x1, y1)
    :param point_2: (x2, y2)
    :return:
    """
    x1, y1 = point_1
    x2, y2 = point_2

    # Equation of a line is y = m * x + c, where m is the slope and c is a constant
    # Find slope me using point 1 and 2
    m = (y2 - y1)/(x2 - x1)
    # Find constant using one point and the slope m in the line equation
    c = y1 - m * x1
    # x = 0 for y intercept
    y_intercept = m * 0 + c
    return y_intercept


if __name__ == '__main__':
    ex_y = ["1", "2", "2.1", "2.1.1", "2.2", "3", "3.1", "3.2", "4", "4.1", "4.1.1", "4.1.1A)", "4.1.1B)", "4.1.2",
            "4.2", "4.2A)", "4.2B)", "4.3", "4.4", "5", "5.1", "5.1.1", "5.1.2", "5.1.2A)", "5.1.2B)", "5.1.3", "5.1.4",
            "5.2", "5.2.1", "5.2.1A)", "5.2.1B)", "5.2.1C)", "5.2.2", "5.2.3", "5.2.3A)", "5.2.3B)", "5.2.4", "5.2.5",
            "6", "6.1", "7", "7.1", "7.2", "8.1", "8.1A)", "8.1B)", "8.2"]

    # Make some fake data
    # Fixing random state for reproducibility
    np.random.seed(19680801)
    # Random data arrays the size of ex_y
    ex_x = list(np.random.randn(len(ex_y)))
    # Make a y-axis based on numbers so we can manipulate it later (ex_y we can use to set the y-tick labels later)
    numerical_y = list(np.arange(0, len(ex_y), 1))

    l_x = [0, 0]
    l_y = [47, 0]

    z_x = [-10, 10]
    z_y = [0, 0]

    # We can find when the line between two points crosses the x-axis
    for idx_point, x_point in enumerate(ex_x):
        # Skip the first value as we need 2 points for a line
        if idx_point == 0:
            continue
        # Get the x and y value of the point before so we can check if it corssed the x-axis
        x_point_before = ex_x[idx_point-1]
        y_point = numerical_y[idx_point]
        y_point_before = numerical_y[idx_point-1]
        # We are looking for when x goes from positive to negative and vice-versa
        if ((x_point < 0) == (x_point_before < 0)) == False:
            # If they are 0.0, we already introduced the 'fake' point so we can skip
            if x_point_before == 0.0 or x_point == 0.0:
                continue
            # The point before
            point_1 = (x_point_before, y_point_before)
            # The current point
            point_2 = (x_point, y_point)
            # Find y value
            y_intercept = find_y_intercept(point_1, point_2)
            # Insert new fake x and y points into our data
            numerical_y.insert(idx_point, y_intercept)
            ex_x.insert(idx_point, 0.0)

    # Create a set of line segments so that we can color them individually
    # This creates the points as a N x 1 x 2 array so that we can stack points
    # together easily to get the segments. The segments array for line collection
    # needs to be numlines x points per line x 2 (x and y)
    points = np.array([ex_x, numerical_y]).T.reshape(-1, 1, 2)
    segments = np.concatenate([points[:-1], points[1:]], axis=1)

    # What color each line segment will be
    linecolors = []
    for line in segments:
        # Check what the combined x value of endpoints of the line would be
        value = line[0][0] + line[1][0]
        # if both endpoints are negative, then that line is red
        color_line = 'r' if value < 0 else 'g'
        linecolors.append(color_line)

    # Create the line collection object, setting the colormapping parameters.
    # Have to set the actual values used for colormapping separately.
    lc = LineCollection(segments, colors=linecolors)
    lc.set_array(ex_x)

    fig, ax = plt.subplots()
    ax.add_collection(lc)
    ax.set_yticks(np.arange(0, len(ex_y), 1))
    ax.set_yticklabels(ex_y)
    for x in range(-10, 10):
        plt.plot(l_x, l_y, color="k", linewidth=2)
    plt.plot(z_x, z_y, color='k', linewidth=1)
    plt.gca().invert_yaxis()
    plt.title("Perfil Neuropsicológico")
    plt.tight_layout()
    plt.grid()
    plt.show()

    # Illustration
    x1, y1 = (-3, 4)
    x2, y2 = (6, 2)

    # Create fake point
    x_fake = 0
    y_fake = find_y_intercept((x1, y1), (x2, y2))

    fig, ax = plt.subplots(nrows=2)
    ax[0].plot([x1, x2], [y1, y2], label="line between point 1 and 2")
    ax[0].plot(x_fake, y_fake, 'o', color='orange', label="new 'fake' point")
    ax[0].plot(x1, y1, 'o', color='r', label="Point 1")
    ax[0].plot(x2, y2, 'o', color='g', label="Point 2")
    ax[0].axvline(x=0, ymin=0, ymax=5, color='k', linewidth=0.5, ls='--')
    ax[0].legend()
    ax[0].set_ylabel('y')
    ax[0].set_xlabel('x')

    ax[1].plot([x1, x_fake], [y1, y_fake], color='r', label="line between point 1 and fake new point")
    ax[1].plot([x2, x_fake], [y2, y_fake], color='g', label="line between fake new point and point 2")
    ax[1].plot(x1, y1, 'o', color='r', label="Point 1")
    ax[1].plot(x2, y2, 'o', color='g', label="Point 2")
    ax[1].plot(x_fake, y_fake, 'o', color='orange', label="new 'fake' point")
    ax[1].axvline(x=0, ymin=0, ymax=5, color='k', linewidth=1, ls='--')
    ax[1].legend()
    ax[1].set_ylabel('y')
    ax[1].set_xlabel('x')
    plt.tight_layout()
    plt.show()

Just thought that if you wanted to include the original points in the graph, you could find where we put the zeros and eliminate them using numpy.where to find the zeros and numpy.delete .只是想如果你想在图表中包含原始点,你可以找到我们放置零的位置并使用numpy.where找到零和numpy.delete消除它们。

ex_x_copy = np.array(ex_x)
zeros = np.where(ex_x_copy == 0.0)
original_ex_x = np.delete(ex_x_copy, zeros)
original_numerical_y = np.delete(numerical_y, zeros)

You couldn't just plot the original ex_x and numerical_y values because we added those 'fake' points so the indices would not match.你不能只是 plot 原始的ex_xnumerical_y值,因为我们添加了那些“假”点,所以索引不匹配。 Then just plot the new 'original' data with the updated indices:然后只有 plot 具有更新索引的新“原始”数据:

fig, ax = plt.subplots()
    ax.add_collection(lc)
    ax.set_yticks(np.arange(0, len(ex_y), 1))
    ax.plot(original_ex_x, original_numerical_y, 'o', color='k', markersize=2)
    ax.set_yticklabels(ex_y)
    for x in range(-10, 10):
        plt.plot(l_x, l_y, color="k", linewidth=2)
    plt.plot(z_x, z_y, color='k', linewidth=1)
    plt.gca().invert_yaxis()
    plt.title("Perfil Neuropsicológico")
    plt.tight_layout()
    plt.grid()
    plt.show()

带“o”点的最终数字

声明:本站的技术帖子网页,遵循CC BY-SA 4.0协议,如果您需要转载,请注明本站网址或者原文地址。任何问题请咨询:yoyou2525@163.com.

 
粤ICP备18138465号  © 2020-2024 STACKOOM.COM