在Python中绘制一系列时间序列的流持续时间曲线

时间:2018-03-15 16:23:49

标签: python matplotlib time-series

流动持续时间曲线是水文学(和其他领域)中可视化时间序列的常用方法。它们可以轻松评估时间序列中的高值和低值以及达到某些值的频率。在Python中有一个简单的方法来绘制它吗?我找不到任何matplotlib工具,这将允许它。也没有其他包装似乎包含它,至少不能轻易绘制一系列流动持续时间曲线。

流量持续时间曲线的示例如下: enter image description here

有关如何创建它的说明,请访问: http://www.renewablesfirst.co.uk/hydropower/hydropower-learning-centre/what-is-a-flow-duration-curve/

因此流量持续时间曲线的基本计算和绘图非常简单。只需计算出超出范围并将其与排序的时间序列进行对比(请参阅ImportanceOfBeingErnest的答案)。如果您有多个时间序列并且想要绘制所有超出概率的值的范围,则会变得更加困难。我在回答这个帖子时提出了一个解决方案,但很高兴听到更优雅的解决方案。我的解决方案还包含一个简单的用作子图,因为通常有不同位置的多个时间序列,必须单独绘制。

我的意思是流量持续时间曲线的范围是这样的: enter image description here

在这里你可以看到三条不同的曲线。黑线是来自河流的测量值,而两个阴影区域是这两个模型的所有模型运​​行的范围。那么,对于几个时间序列,计算和绘制一系列流动持续时间曲线最简单的方法是什么?

2 个答案:

答案 0 :(得分:3)

如果我正确理解流动持续时间曲线的概念,您只需将流程绘制为超出范围的函数。

filter()

enter image description here

由此可以很容易地看出,预计60%的时间流量为11或更大。

<小时/> 如果有多个数据集,可以使用+----+----+----+----+---+ |col1|col2|col3|col4| d| +----+----+----+----+---+ | A| xx| D| vv| 4| | A| x| A| xx| 3| | E| xxx| B| vv| 3| | F|xxxx| F| vvv| 4| | G| xxx| G| xx| 4| +----+----+----+----+---+ 将它们绘制为范围。

import numpy as np
import matplotlib.pyplot as plt

data = np.random.rayleigh(10,144)

sort = np.sort(data)[::-1]
exceedence = np.arange(1.,len(sort)+1) / len(sort)

plt.plot(exceedence*100, sort)
plt.xlabel("Exceedence [%]")
plt.ylabel("Flow rate")
plt.show()

enter image description here

答案 1 :(得分:0)

编辑:由于我的第一个答案是过于复杂和不优雅,我重新编写它以包含ImportanceOfBeingErnest的解决方案。我仍然保留新版本,以及ImportanceOfBeingErnest的新版本,因为我认为附加功能可能使其他人更容易为他们的时间序列绘制流持续时间曲线。如果有人可能有其他想法,请参阅:Github Repository

功能是:

  • 更改范围流持续时间曲线的百分位数

  • 作为独立图或子图使用方便。如果提供了子绘图对象,则在此绘制流动持续时间曲线。当提供无时,它会创建一个并将其返回

  • 为范围曲线分配kwargs及其比较

  • 使用关键字

  • 将y轴更改为对数刻度
  • 有助于了解其用法的扩展示例。

代码如下:

# -*- coding: utf-8 -*-
"""
Created on Thu Mar 15 10:09:13 2018

@author: Florian Ulrich Jehn
"""
import matplotlib.pyplot as plt
import pandas as pd
import numpy as np


def flow_duration_curve(x, comparison=None, axis=0, ax=None, plot=True, 
                        log=True, percentiles=(5, 95), decimal_places=1,
                        fdc_kwargs=None, fdc_range_kwargs=None, 
                        fdc_comparison_kwargs=None):
    """
    Calculates and plots a flow duration curve from x. 

    All observations/simulations are ordered and the empirical probability is
    calculated. This is then plotted as a flow duration curve. 

    When x has more than one dimension along axis, a range flow duration curve 
    is plotted. This means that for every probability a min and max flow is 
    determined. This is then plotted as a fill between. 

    Additionally a comparison can be given to the function, which is plotted in
    the same ax.

    :param x: numpy array or pandas dataframe, discharge of measurements or 
    simulations
    :param comparison: numpy array or pandas dataframe of discharge that should
    also be plotted in the same ax
    :param axis: int, axis along which x is iterated through
    :param ax: matplotlib subplot object, if not None, will plot in that 
    instance
    :param plot: bool, if False function will not show the plot, but simply
    return the ax object
    :param log: bool, if True plot on loglog axis
    :param percentiles: tuple of int, percentiles that should be used for 
    drawing a range flow duration curve
    :param fdc_kwargs: dict, matplotlib keywords for the normal fdc
    :param fdc_range_kwargs: dict, matplotlib keywords for the range fdc
    :param fdc_comparison_kwargs: dict, matplotlib keywords for the comparison 
    fdc

    return: subplot object with the flow duration curve in it
    """
    # Convert x to an pandas dataframe, for easier handling
    if not isinstance(x, pd.DataFrame):
        x = pd.DataFrame(x)

    # Get the dataframe in the right dimensions, if it is not in the expected
    if axis != 0:
        x = x.transpose()

    # Convert comparison to a dataframe as well
    if comparison is not None and not isinstance(comparison, pd.DataFrame):
        comparison = pd.DataFrame(comparison)
        # And transpose it is neccesary
        if axis != 0:
            comparison = comparison.transpose()

    # Create an ax is neccesary
    if ax is None:
        fig, ax = plt.subplots(1,1)

    # Make the y scale logarithmic if needed
    if log:
        ax.set_yscale("log")

    # Determine if it is a range flow curve or a normal one by checking the 
    # dimensions of the dataframe
    # If it is one, make a single fdc
    if x.shape[1] == 1:
        plot_single_flow_duration_curve(ax, x[0], fdc_kwargs)   

    # Make a range flow duration curve
    else:
        plot_range_flow_duration_curve(ax, x, percentiles, fdc_range_kwargs)

    # Add a comparison to the plot if is present
    if comparison is not None:
        ax = plot_single_flow_duration_curve(ax, comparison[0], 
                                             fdc_comparison_kwargs)    

    # Name the x-axis
    ax.set_xlabel("Exceedence [%]")

    # show if requested
    if plot:
        plt.show()

    return ax


def plot_single_flow_duration_curve(ax, timeseries, kwargs):
    """
    Plots a single fdc into an ax.

    :param ax: matplotlib subplot object
    :param timeseries: list like iterable
    :param kwargs: dict, keyword arguments for matplotlib

    return: subplot object with a flow duration curve drawn into it
    """
    # Get the probability
    exceedence = np.arange(1., len(timeseries) + 1) / len(timeseries)
    exceedence *= 100
    # Plot the curve, check for empty kwargs
    if kwargs is not None:
        ax.plot(exceedence, sorted(timeseries, reverse=True), **kwargs)
    else:
        ax.plot(exceedence, sorted(timeseries, reverse=True))
    return ax


def plot_range_flow_duration_curve(ax, x, percentiles, kwargs):
    """
    Plots a single range fdc into an ax.

    :param ax: matplotlib subplot object
    :param x: dataframe of several timeseries
    :param decimal_places: defines how finely grained the range flow duration 
    curve is calculated and drawn. A low values makes it more finely grained.
    A value which is too low might create artefacts.
    :param kwargs: dict, keyword arguments for matplotlib

    return: subplot object with a range flow duration curve drawn into it
    """
    # Get the probabilites
    exceedence = np.arange(1.,len(np.array(x))+1) /len(np.array(x))
    exceedence *= 100

    # Sort the data
    sort = np.sort(x, axis=0)[::-1]

    # Get the percentiles
    low_percentile = np.percentile(sort, percentiles[0], axis=1)
    high_percentile = np.percentile(sort, percentiles[1], axis=1)

    # Plot it, check for empty kwargs
    if kwargs is not None:
        ax.fill_between(exceedence, low_percentile, high_percentile, **kwargs)
    else:
        ax.fill_between(exceedence, low_percentile, high_percentile)
    return ax

如何使用它:

# Create test data
np_array_one_dim = np.random.rayleigh(5, [1, 300])
np_array_75_dim = np.c_[np.random.rayleigh(11 ,[25, 300]),
                        np.random.rayleigh(10, [25, 300]),
                        np.random.rayleigh(8, [25, 300])]
df_one_dim = pd.DataFrame(np.random.rayleigh(9, [1, 300]))
df_75_dim = pd.DataFrame(np.c_[np.random.rayleigh(8, [25, 300]),
                               np.random.rayleigh(15, [25, 300]),
                               np.random.rayleigh(3, [25, 300])])
df_75_dim_transposed = pd.DataFrame(np_array_75_dim.transpose())

# Call the function with all different arguments
fig, subplots = plt.subplots(nrows=2, ncols=3)
ax1 = flow_duration_curve(np_array_one_dim, ax=subplots[0,0], plot=False,
                          axis=1, fdc_kwargs={"linewidth":0.5})
ax1.set_title("np array one dim\nwith kwargs")

ax2 = flow_duration_curve(np_array_75_dim, ax=subplots[0,1], plot=False,
                          axis=1, log=False, percentiles=(0,100))
ax2.set_title("np array 75 dim\nchanged percentiles\nnolog")

ax3 = flow_duration_curve(df_one_dim, ax=subplots[0,2], plot=False, axis=1,
                          log=False, fdc_kwargs={"linewidth":0.5})
ax3.set_title("\ndf one dim\nno log\nwith kwargs")

ax4 = flow_duration_curve(df_75_dim, ax=subplots[1,0], plot=False, axis=1,
                          log=False)
ax4.set_title("df 75 dim\nno log")

ax5 = flow_duration_curve(df_75_dim_transposed, ax=subplots[1,1], 
                          plot=False)
ax5.set_title("df 75 dim transposed")

ax6 = flow_duration_curve(df_75_dim, ax=subplots[1,2], plot=False,
                          comparison=np_array_one_dim, axis=1, 
                          fdc_comparison_kwargs={"color":"black", 
                                                 "label":"comparison",
                                                 "linewidth":0.5},
                          fdc_range_kwargs={"label":"range_fdc"})
ax6.set_title("df 75 dim\n with comparison\nwith kwargs")
ax6.legend()

# Show the beauty
fig.tight_layout()
plt.show()

结果如下: enter image description here