Matplotlib和Numpy - 创建日历热图

cod*_*000 6 python numpy matplotlib python-2.7

是否可以在不使用熊猫的情况下创建日历热图?如果是这样,有人可以发一个简单的例子吗?

我有8月16日的日期和16的计数值,我认为这将是一种快速简便的方法,可以显示很长一段时间内天数的强度.

谢谢

小智 27

免责声明:这是我自己的包的插件。虽然我晚了几年才帮助OP,但我希望其他人会发现它有用。

我对一个相关问题做了一些挖掘。当我找不到满足我所有要求的任何其他包时,我最终为此目的编写了一个新包。

该软件包尚未完善,文档也很少,但我还是将其发布在 PyPi 上,以供其他人使用。欢迎您提供任何反馈,无论是在这里还是在我的GitHub上。

七月

该软件包july可以通过 pip 调用并安装:

$ pip install july
Run Code Online (Sandbox Code Playgroud)

以下是直接来自自述文件的一些用例:

导入包并生成数据
import numpy as np
import july
from july.utils import date_range

dates = date_range("2020-01-01", "2020-12-31")
data = np.random.randint(0, 14, len(dates))
Run Code Online (Sandbox Code Playgroud)
GitHub 活动如情节:
july.heatmap(dates, data, title='Github Activity', cmap="github")
Run Code Online (Sandbox Code Playgroud)

在此输入图像描述

连续数据的每日热图(带颜色条):
july.heatmap(
    osl_df.date, # Here, osl_df is a pandas data frame.
    osl_df.temp, 
    cmap="golden", 
    colorbar=True, 
    title="Average temperatures: Oslo , Norway"
)
Run Code Online (Sandbox Code Playgroud)

在此输入图像描述

每个月的大纲month_grid=True
july.heatmap(dates=dates, 
             data=data, 
             cmap="Pastel1",
             month_grid=True, 
             horizontal=True,
             value_label=False,
             date_label=False,
             weekday_label=True,
             month_label=True, 
             year_label=True,
             colorbar=False,
             fontfamily="monospace",
             fontsize=12,
             title=None,
             titlesize="large",
             dpi=100)
Run Code Online (Sandbox Code Playgroud)

在此输入图像描述

最后,您还可以创建月份或日历图:

# july.month_plot(dates, data, month=5) # This will plot only May.
july.calendar_plot(dates, data)
Run Code Online (Sandbox Code Playgroud)

在此输入图像描述

类似的包:

  • calplot汤姆·郭 (Tom Kwok) 着.
    • GitHub:链接
    • 安装:pip install calplot
    • 积极维护且比july.
    • 以熊猫为中心,采用包含日期和值的熊猫系列。
    • 如果您只想寻找热图功能并且不需要month_plot或,那么这是非常好的选择calendar_plot
  • calmap作者:马丁·维尔马特。
    • GitHub:链接
    • 安装:pip install calmap
    • 从中产生的包裹calplot
    • 似乎需要更长时间的积极维护。


Joe*_*ton 20

这当然是可能的,但你需要跳过一些箍.

首先,我假设您的意思是日历显示看起来像日历,而不是更线性的格式(线性格式化的"热图"比这更容易).

关键是将任意长度的1D系列重塑为Nx7 2D阵列,其中每行为一周,列为天.这很容易,但你还需要正确标记数月和数天,这可能会让人感到啰嗦.

这是一个例子.它甚至没有远程尝试跨越年度跨越(例如2014年12月至2015年1月等).但是,希望它可以让你开始:

import datetime as dt
import matplotlib.pyplot as plt
import numpy as np

def main():
    dates, data = generate_data()
    fig, ax = plt.subplots(figsize=(6, 10))
    calendar_heatmap(ax, dates, data)
    plt.show()

def generate_data():
    num = 100
    data = np.random.randint(0, 20, num)
    start = dt.datetime(2015, 3, 13)
    dates = [start + dt.timedelta(days=i) for i in range(num)]
    return dates, data

def calendar_array(dates, data):
    i, j = zip(*[d.isocalendar()[1:] for d in dates])
    i = np.array(i) - min(i)
    j = np.array(j) - 1
    ni = max(i) + 1

    calendar = np.nan * np.zeros((ni, 7))
    calendar[i, j] = data
    return i, j, calendar


def calendar_heatmap(ax, dates, data):
    i, j, calendar = calendar_array(dates, data)
    im = ax.imshow(calendar, interpolation='none', cmap='summer')
    label_days(ax, dates, i, j, calendar)
    label_months(ax, dates, i, j, calendar)
    ax.figure.colorbar(im)

def label_days(ax, dates, i, j, calendar):
    ni, nj = calendar.shape
    day_of_month = np.nan * np.zeros((ni, 7))
    day_of_month[i, j] = [d.day for d in dates]

    for (i, j), day in np.ndenumerate(day_of_month):
        if np.isfinite(day):
            ax.text(j, i, int(day), ha='center', va='center')

    ax.set(xticks=np.arange(7), 
           xticklabels=['M', 'T', 'W', 'R', 'F', 'S', 'S'])
    ax.xaxis.tick_top()

def label_months(ax, dates, i, j, calendar):
    month_labels = np.array(['Jan', 'Feb', 'Mar', 'Apr', 'May', 'Jun', 'Jul',
                             'Aug', 'Sep', 'Oct', 'Nov', 'Dec'])
    months = np.array([d.month for d in dates])
    uniq_months = sorted(set(months))
    yticks = [i[months == m].mean() for m in uniq_months]
    labels = [month_labels[m - 1] for m in uniq_months]
    ax.set(yticks=yticks)
    ax.set_yticklabels(labels, rotation=90)

main()
Run Code Online (Sandbox Code Playgroud)

在此输入图像描述


cba*_*ick 9

编辑:我现在看到问题要求没有大熊猫的情节.即便如此,这个问题是谷歌搜索"python calendar heatmap"的第一页,所以我将这里留下.无论如何,我建议使用熊猫.您可能已将它作为另一个包的依赖项,并且pandas具有用于处理datetime数据(pandas.Timestamppandas.DatetimeIndex)的最佳API .

我可以为这些图找到的唯一Python包calmap是未维护的,并且与最近的matplotlib不兼容.所以我决定写自己的.它产生如下图:

日历热图 这是代码.输入是一个带有日期时间索引的系列,给出热图的值:

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


DAYS = ['Sun.', 'Mon.', 'Tues.', 'Wed.', 'Thurs.', 'Fri.', 'Sat.']
MONTHS = ['Jan.', 'Feb.', 'Mar.', 'Apr.', 'May', 'June', 'July', 'Aug.', 'Sept.', 'Oct.', 'Nov.', 'Dec.']


def date_heatmap(series, start=None, end=None, mean=False, ax=None, **kwargs):
    '''Plot a calendar heatmap given a datetime series.

    Arguments:
        series (pd.Series):
            A series of numeric values with a datetime index. Values occurring
            on the same day are combined by sum.
        start (Any):
            The first day to be considered in the plot. The value can be
            anything accepted by :func:`pandas.to_datetime`. The default is the
            earliest date in the data.
        end (Any):
            The last day to be considered in the plot. The value can be
            anything accepted by :func:`pandas.to_datetime`. The default is the
            latest date in the data.
        mean (bool):
            Combine values occurring on the same day by mean instead of sum.
        ax (matplotlib.Axes or None):
            The axes on which to draw the heatmap. The default is the current
            axes in the :module:`~matplotlib.pyplot` API.
        **kwargs:
            Forwarded to :meth:`~matplotlib.Axes.pcolormesh` for drawing the
            heatmap.

    Returns:
        matplotlib.collections.Axes:
            The axes on which the heatmap was drawn. This is set as the current
            axes in the `~matplotlib.pyplot` API.
    '''
    # Combine values occurring on the same day.
    dates = series.index.floor('D')
    group = series.groupby(dates)
    series = group.mean() if mean else group.sum()

    # Parse start/end, defaulting to the min/max of the index.
    start = pd.to_datetime(start or series.index.min())
    end = pd.to_datetime(end or series.index.max())

    # We use [start, end) as a half-open interval below.
    end += np.timedelta64(1, 'D')

    # Get the previous/following Sunday to start/end.
    # Pandas and numpy day-of-week conventions are Monday=0 and Sunday=6.
    start_sun = start - np.timedelta64((start.dayofweek + 1) % 7, 'D')
    end_sun = end + np.timedelta64(7 - end.dayofweek - 1, 'D')

    # Create the heatmap and track ticks.
    num_weeks = (end_sun - start_sun).days // 7
    heatmap = np.zeros((7, num_weeks))
    ticks = {}  # week number -> month name
    for week in range(num_weeks):
        for day in range(7):
            date = start_sun + np.timedelta64(7 * week + day, 'D')
            if date.day == 1:
                ticks[week] = MONTHS[date.month - 1]
            if date.dayofyear == 1:
                ticks[week] += f'\n{date.year}'
            if start <= date < end:
                heatmap[day, week] = series.get(date, 0)

    # Get the coordinates, offset by 0.5 to align the ticks.
    y = np.arange(8) - 0.5
    x = np.arange(num_weeks + 1) - 0.5

    # Plot the heatmap. Prefer pcolormesh over imshow so that the figure can be
    # vectorized when saved to a compatible format. We must invert the axis for
    # pcolormesh, but not for imshow, so that it reads top-bottom, left-right.
    ax = ax or plt.gca()
    mesh = ax.pcolormesh(x, y, heatmap, **kwargs)
    ax.invert_yaxis()

    # Set the ticks.
    ax.set_xticks(list(ticks.keys()))
    ax.set_xticklabels(list(ticks.values()))
    ax.set_yticks(np.arange(7))
    ax.set_yticklabels(DAYS)

    # Set the current image and axes in the pyplot API.
    plt.sca(ax)
    plt.sci(mesh)

    return ax


def date_heatmap_demo():
    '''An example for `date_heatmap`.

    Most of the sizes here are chosen arbitrarily to look nice with 1yr of
    data. You may need to fiddle with the numbers to look right on other data.
    '''
    # Get some data, a series of values with datetime index.
    data = np.random.randint(5, size=365)
    data = pd.Series(data)
    data.index = pd.DatetimeIndex(start='2017-01-01', end='2017-12-31', freq='1D')

    # Create the figure. For the aspect ratio, one year is 7 days by 53 weeks.
    # We widen it further to account for the tick labels and color bar.
    figsize = plt.figaspect(7 / 56)
    fig = plt.figure(figsize=figsize)

    # Plot the heatmap with a color bar.
    ax = date_heatmap(data, edgecolor='black')
    plt.colorbar(ticks=range(5), pad=0.02)

    # Use a discrete color map with 5 colors (the data ranges from 0 to 4).
    # Extending the color limits by 0.5 aligns the ticks in the color bar.
    cmap = mpl.cm.get_cmap('Blues', 5)
    plt.set_cmap(cmap)
    plt.clim(-0.5, 4.5)

    # Force the cells to be square. If this is set, the size of the color bar
    # may look weird compared to the size of the heatmap. That can be corrected
    # by the aspect ratio of the figure or scale of the color bar.
    ax.set_aspect('equal')

    # Save to a file. For embedding in a LaTeX doc, consider the PGF backend.
    # http://sbillaudelle.de/2015/02/23/seamlessly-embedding-matplotlib-output-into-latex.html
    fig.savefig('heatmap.pdf', bbox_inches='tight')

    # The firgure must be explicitly closed if it was not shown.
    plt.close(fig)
Run Code Online (Sandbox Code Playgroud)

  • 这看起来真的不错!github 上有公共仓库吗? (2认同)

SO_*_*ist 8

我希望创建一个日历热图,其中每个月都单独显示。我还需要用天数(day_of_month)和它的值标签来注释每一天。

我受到这里发布的答案以及以下网站的启发:

在这里,虽然在 R

使用 pcolormesh 的热图

但是,我似乎没有找到与我正在寻找的完全一样的东西,所以我决定在这里发布我的解决方案,以便在某些时候拯救其他人想要同样的情节。

我的示例使用了一些 Pandas 来生成一些虚拟数据,因此您可以轻松地插入自己的数据源。除此之外,它只是 matplotlib。

下面给出了代码的输出。根据我的需要,我还想突出显示数据为 0 的日子(参见 1 月 1 日)。 在此处输入图片说明

import numpy as np
import pandas as pd
import matplotlib.pyplot as plt
from matplotlib.patches import Polygon

# Settings
years = [2018] # [2018, 2019, 2020]
weeks = [1, 2, 3, 4, 5, 6]
days = ['M', 'T', 'W', 'T', 'F', 'S', 'S']
month_names = ['January', 'February', 'March', 'April', 'May', 'June', 'July', 'August',
               'September', 'October', 'November', 'December']

def generate_data():
    idx = pd.date_range('2018-01-01', periods=365, freq='D')
    return pd.Series(range(len(idx)), index=idx)


def split_months(df, year):
    """
    Take a df, slice by year, and produce a list of months,
    where each month is a 2D array in the shape of the calendar
    :param df: dataframe or series
    :return: matrix for daily values and numerals
    """
    df = df[df.index.year == year]


    # Empty matrices
    a = np.empty((6, 7))
    a[:] = np.nan

    day_nums = {m:np.copy(a) for m in range(1,13)}  # matrix for day numbers
    day_vals = {m:np.copy(a) for m in range(1,13)}  # matrix for day values

    # Logic to shape datetimes to matrices in calendar layout
    for d in df.iteritems():  # use iterrows if you have a DataFrame

        day = d[0].day
        month = d[0].month
        col = d[0].dayofweek

        if d[0].is_month_start:
            row = 0

        day_nums[month][row, col] = day  # day number (0-31)
        day_vals[month][row, col] = d[1] # day value (the heatmap data)

        if col == 6:
            row += 1

    return day_nums, day_vals


def create_year_calendar(day_nums, day_vals):
    fig, ax = plt.subplots(3, 4, figsize=(14.85, 10.5))

    for i, axs in enumerate(ax.flat):

        axs.imshow(day_vals[i+1], cmap='viridis', vmin=1, vmax=365)  # heatmap
        axs.set_title(month_names[i])

        # Labels
        axs.set_xticks(np.arange(len(days)))
        axs.set_xticklabels(days, fontsize=10, fontweight='bold', color='#555555')
        axs.set_yticklabels([])

        # Tick marks
        axs.tick_params(axis=u'both', which=u'both', length=0)  # remove tick marks
        axs.xaxis.tick_top()

        # Modify tick locations for proper grid placement
        axs.set_xticks(np.arange(-.5, 6, 1), minor=True)
        axs.set_yticks(np.arange(-.5, 5, 1), minor=True)
        axs.grid(which='minor', color='w', linestyle='-', linewidth=2.1)

        # Despine
        for edge in ['left', 'right', 'bottom', 'top']:
            axs.spines[edge].set_color('#FFFFFF')

        # Annotate
        for w in range(len(weeks)):
            for d in range(len(days)):
                day_val = day_vals[i+1][w, d]
                day_num = day_nums[i+1][w, d]

                # Value label
                axs.text(d, w+0.3, f"{day_val:0.0f}",
                         ha="center", va="center",
                         fontsize=7, color="w", alpha=0.8)

                # If value is 0, draw a grey patch
                if day_val == 0:
                    patch_coords = ((d - 0.5, w - 0.5),
                                    (d - 0.5, w + 0.5),
                                    (d + 0.5, w + 0.5),
                                    (d + 0.5, w - 0.5))

                    square = Polygon(patch_coords, fc='#DDDDDD')
                    axs.add_artist(square)

                # If day number is a valid calendar day, add an annotation
                if not np.isnan(day_num):
                    axs.text(d+0.45, w-0.31, f"{day_num:0.0f}",
                             ha="right", va="center",
                             fontsize=6, color="#003333", alpha=0.8)  # day

                # Aesthetic background for calendar day number
                patch_coords = ((d-0.1, w-0.5),
                                (d+0.5, w-0.5),
                                (d+0.5, w+0.1))

                triangle = Polygon(patch_coords, fc='w', alpha=0.7)
                axs.add_artist(triangle)

    # Final adjustments
    fig.suptitle('Calendar', fontsize=16)
    plt.subplots_adjust(left=0.04, right=0.96, top=0.88, bottom=0.04)

    # Save to file
    plt.savefig('calendar_example.pdf')


for year in years:
    df = generate_data()
    day_nums, day_vals = split_months(df, year)
    create_year_calendar(day_nums, day_vals)
Run Code Online (Sandbox Code Playgroud)

可能有很大的优化空间,但这得到了我需要做的事情。

  • 这看起来很漂亮,我喜欢它!我只需要进行一些调整即可将其用于我的目的,但这很容易做到,因为您的代码结构良好且注释良好 (2认同)