【问题标题】:Plot Multicolored line based on conditional in python在python中根据条件绘制多色线
【发布时间】:2015-10-13 22:12:26
【问题描述】:

我有一个包含三列和一个日期时间索引的 pandas 数据框

date        px_last  200dma     50dma           
2014-12-24  2081.88 1953.16760  2019.2726
2014-12-26  2088.77 1954.37975  2023.7982
2014-12-29  2090.57 1955.62695  2028.3544
2014-12-30  2080.35 1956.73455  2032.2262
2014-12-31  2058.90 1957.66780  2035.3240

如果在给定的一天 50dma 高于 200dma 值,我想绘制 'px_last' 列的时间序列图,如果 50dma 值低于 200dma 值,则绘制为红色。我看过这个例子,但似乎无法让它适用于我的情况 http://matplotlib.org/examples/pylab_examples/multicolored_line.html

【问题讨论】:

  • 任何人有办法将其扩展到 3 种或更多颜色?

标签: python pandas matplotlib plot


【解决方案1】:

基于@Jianxun Li 的回答,这是一个更容易扩展到 3 种以上颜色的版本:

import numpy as np
import pandas as pd
import matplotlib.pyplot as plt


# Simulate data
np.random.seed(1234)
df = pd.DataFrame(
    {'px_last': 100 + np.random.randn(1000).cumsum()},
    index=pd.date_range('2010-01-01', periods=1000, freq='B'),
)
df['50dma'] = df['px_last'].rolling(window=50, center=False).mean()
df['200dma'] = df['px_last'].rolling(window=200, center=False).mean()

## Apply labels
df['label'] = 'out of bounds'
df.loc[abs(df['50dma'] - df['200dma']) >= 7, 'label'] = '|50dma - 200dma| >= 7'
df.loc[abs(df['50dma'] - df['200dma']) < 7, 'label'] = '|50dma - 200dma| < 7'
df.loc[abs(df['50dma'] - df['200dma']) < 5, 'label'] = '|50dma - 200dma| < 5'
df.loc[abs(df['50dma'] - df['200dma']) < 3, 'label'] = '|50dma - 200dma| < 3'
df = df[df['label'] != 'out of bounds']

## Convert labels to colors
label2color = {
    '|50dma - 200dma| < 3': 'green',
    '|50dma - 200dma| < 5': 'yellow',
    '|50dma - 200dma| < 7': 'orange',
    '|50dma - 200dma| >= 7': 'red',
}
df['color'] = df['label'].apply(lambda label: label2color[label])

# Create plot
fig, ax = plt.subplots()

def gen_repeating(s):
    """Generator: groups repeated elements in an iterable
    E.g.
        'abbccc' -> [('a', 0, 0), ('b', 1, 2), ('c', 3, 5)]
    """
    i = 0
    while i < len(s):
        j = i
        while j < len(s) and s[j] == s[i]:
            j += 1
        yield (s[i], i, j-1)
        i = j

## Add px_last lines
for color, start, end in gen_repeating(df['color']):
    if start > 0: # make sure lines connect
        start -= 1
    idx = df.index[start:end+1]
    df.loc[idx, 'px_last'].plot(ax=ax, color=color, label='')

## Add 50dma and 200dma lines
df['50dma'].plot(ax=ax, color='k', ls='--', label='MA$_{50}$')
df['200dma'].plot(ax=ax, color='b', ls='--', label='MA$_{200}$')

## Get artists and labels for legend and chose which ones to display
handles, labels = ax.get_legend_handles_labels()

## Create custom artists
g_line = plt.Line2D((0,1),(0,0), color='green')
y_line = plt.Line2D((0,1),(0,0), color='yellow')
o_line = plt.Line2D((0,1),(0,0), color='orange')
r_line = plt.Line2D((0,1),(0,0), color='red')

## Create legend from custom artist/label lists
ax.legend(
    handles + [g_line, y_line, o_line, r_line],
    labels + [
        '|MA$_{50} - $MA$_{200}| < 3$',
        '|MA$_{50} - $MA$_{200}| < 5$',
        '|MA$_{50} - $MA$_{200}| < 7$',
        '|MA$_{50} - $MA$_{200}| \geq 7$',
    ],
    loc='best',
)

# Display plot
plt.show()

我还添加了一个花哨的传说。

【讨论】:

    【解决方案2】:

    这是一个不使用matplotlib.collections.LineCollection 的示例。这个想法是首先识别交叉点,然后通过 groupby 使用plot 函数。

    import pandas as pd
    import numpy as np
    import matplotlib.pyplot as plt
    
    # simulate data
    # =============================
    np.random.seed(1234)
    df = pd.DataFrame({'px_last': 100 + np.random.randn(1000).cumsum()}, index=pd.date_range('2010-01-01', periods=1000, freq='B'))
    df['50dma'] = pd.rolling_mean(df['px_last'], window=50)
    df['200dma'] = pd.rolling_mean(df['px_last'], window=200)
    df['label'] = np.where(df['50dma'] > df['200dma'], 1, -1)
    
    
    # plot
    # =============================
    df = df.dropna(axis=0, how='any')
    
    fig, ax = plt.subplots()
    
    def plot_func(group):
        global ax
        color = 'r' if (group['label'] < 0).all() else 'g'
        lw = 2.0
        ax.plot(group.index, group.px_last, c=color, linewidth=lw)
    
    df.groupby((df['label'].shift() * df['label'] < 0).cumsum()).apply(plot_func)
    
    # add ma lines
    ax.plot(df.index, df['50dma'], 'k--', label='MA-50')
    ax.plot(df.index, df['200dma'], 'b--', label='MA-200')
    ax.legend(loc='best')
    

    【讨论】:

    • 我得到了一个 StopIteration 错误,因为 plot_func 没有返回任何东西,所以我只是将这一行添加到函数返回组中,这是完美的。谢谢!
    • @Mishiko 这很奇怪。代码在我的电脑上正常运行。如果不是专有的,您可以上传您的示例数据(通过 Dropbox 共享链接或谷歌驱动程序)吗?
    • 我在使用您的随机数据时遇到了同样的错误,但是是的,我将在今天晚些时候上传一个指向我的一些示例数据的链接。
    • @Mishiko 确保您拥有最新版本的pandasmatplotlib。 :-)
    • 是否可以将其扩展到 3 种或更多颜色?
    猜你喜欢
    • 1970-01-01
    • 1970-01-01
    • 1970-01-01
    • 1970-01-01
    • 2022-12-11
    • 2017-02-25
    • 1970-01-01
    • 1970-01-01
    相关资源
    最近更新 更多