跳转到主要内容

通过Django REST框架提供pandas数据框,用于客户端(即d3.js)可视化

项目描述

Django REST Pandas

Django REST Framework + pandas = 基于模型的可视化API

Django REST Pandas (DRP) 通过Django REST框架提供一种简单的方法来生成和提供pandas DataFrame。生成的API可以提供CSV(以及其他其他格式),供客户端可视化工具(如d3.js)使用。

DRP的设计理念强制执行数据与表示之间的严格分离。这保持了实现简单,同时也具有提供一个简单的方法来提供可视化源数据的良好副作用。通常可以通过发送用户到可视化代码内部用于加载数据的相同URL来实现这一功能。

DRP 不包含任何 JavaScript 代码,将交互式可视化的实现留给了实施者。话虽如此,DRP 常与 wq.app 库一起使用,该库提供了 wq/chart.jswq/pandas.js,这是一个与 DRP 提供的 CSV 数据配合良好的图表函数和数据加载器集合。

Latest PyPI Release Release Notes License GitHub Stars GitHub Forks GitHub Issues

Travis Build Status Python Support Django Support

实时演示

climata-viewer 项目使用 Django REST Pandas 和 wq/chart.js 提供交互式可视化和电子表格下载。

相关工作

Python 驱动的数据分析与可视化领域正在增长,有许多类似解决方案可能更适合您的需求。

  • Django Pandas 提供了一个支持 pandas 的自定义 ORM 模型管理器。相比之下,Django REST Pandas 在 视图 层面上工作,通过集成 pandas 来使用自定义 Django REST 框架序列化和渲染器。
  • DRF-CSV 为 Django REST 框架提供了直接的 CSV 渲染器。如果您只想使用 CSV API 而不需要 pandas DataFrame 功能,它可能很有用。
  • mpld3 提供了从 matplotlibd3.js 的直接桥梁,包括无缝的 IPython 集成。它仅限于(大型)matplotlib 图表词汇表,但对于许多用例应该足够。
  • Bokeh 是一个完整的客户端-服务器可视化平台。它不利用 d3 或 Django,但作为一个全面的、前瞻性的方法来处理类似用例而闻名。

Django REST Pandas 的目标是提供一种通用的 REST API,用于提供 pandas 数据帧。在这方面,它与 Bokeh 中的 Plot Server 类似,但更加通用,因为它不假设任何特定的可视化格式或技术。此外,DRP 优化了与面向公众的 Django 驱动的网站的集成(与主要用于 IPython 的 mpld3 不同)。

总之,DRP 适用于以下用例:

  • 您希望支持实时电子表格下载以及交互式可视化,或者
  • 您希望完全控制客户端可视化堆栈,以便将其集成到您的整个网站和/或构建过程中。这通常意味着手动编写 JavaScript 代码。如果您更熟悉(I)Python 并需要能够生成交互式可视化的东西,那么 mpld3 可能是一个更好的选择。

支持的格式

默认提供以下输出格式。这些作为 渲染器类 提供,以便利用 Django REST 框架内置的内容类型协商。这意味着客户端可以通过以下方式指定格式:

  • HTTP "Accept" 标头 (Accept: text/csv),
  • 格式参数 (/path?format=csv),或者
  • 格式扩展 (/path.csv)

默认情况下,每个 pandas 视图都启用了 HTTP 标头和格式参数。使用扩展需要自定义 URL 配置(见下文)。

格式 内容类型 pandas DataFrame 函数 注意
HTML text/html to_html() 请参阅有关 HTML 输出 的说明
CSV text/csv to_csv()  
TXT text/plain to_csv() 对于测试很有用,因为大多数浏览器会下载 CSV 文件而不是显示它
JSON application/json to_json() date_formatorient 可在 URL 中提供(例如 /path.json?orient=columns
XLSX application/vnd.openxml...sheet to_excel()  
XLS application/vnd.ms-excel to_excel()  
PNG image/png plot() 目前不可定制的程度较高,但可以通过简单的方式将数据以图像形式查看。
SVG image/svg plot() 最终,这些可以成为无法处理d3.js客户端的回退选项。

底层实现是一系列序列化器,它们将正常的序列化结果放入数据框中。然后,包含的渲染器使用内置的pandas功能生成输出。

使用方法

入门

# Recommended: create virtual environment
# python3 -m venv venv
# . venv/bin/activate
pip install rest-pandas

注意: Django REST Pandas依赖于pandas,而pandas本身依赖于C语言编写的NumPy和其他科学Python库。这通常没有问题,因为pip可以使用Python Wheel来安装预编译版本。如果您由于依赖问题而无法安装DRP,您可能需要使用apt或conda预先安装pandas。

使用示例

无模型

下面的示例允许您为现有的Pandas DataFrame创建一个简单的API,例如由现有文件生成。

# views.py
from rest_pandas import PandasSimpleView
import pandas as pd

class TimeSeriesView(PandasSimpleView):
    def get_data(self, request, *args, **kwargs):
        return pd.read_csv('data.csv')

基于模型

下面的示例假设您已经设置了一个包含单个TimeSeries模型的Django项目。

# views.py
from rest_pandas import PandasView
from .models import TimeSeries
from .serializers import TimeSeriesSerializer

# Short version (leverages default DRP settings):
class TimeSeriesView(PandasView):
    queryset = TimeSeries.objects.all()
    serializer_class = TimeSeriesSerializer
    # That's it!  The view will be able to export the model dataset to any of
    # the included formats listed above.  No further customization is needed to
    # leverage the defaults.

# Long Version and step-by-step explanation
class TimeSeriesView(PandasView):
    # Assign a default model queryset to the view
    queryset = TimeSeries.objects.all()

    # Step 1. In response to get(), the underlying Django REST Framework view
    # will load the queryset and then pass it to the following function.
    def filter_queryset(self, qs): 
        # At this point, you can filter queryset based on self.request or other
        # settings (useful for limiting memory usage).  This function can be
        # omitted if you are using a filter backend or do not need filtering.
        return qs

    # Step 2. A Django REST Framework serializer class should serialize each
    # row in the queryset into a simple dict format.  A simple ModelSerializer
    # should be sufficient for most cases.
    serializer_class = TimeSeriesSerializer  # extends ModelSerializer

    # Step 3.  The included PandasSerializer will load all of the row dicts
    # into array and convert the array into a pandas DataFrame.  The DataFrame
    # is essentially an intermediate format between Step 2 (dict) and Step 4
    # (output format).  The default DataFrame simply maps each model field to a
    # column heading, and will be sufficient in many cases.  If you do not need
    # to transform the dataframe, you can skip to step 4.

    # If you would like to transform the dataframe (e.g. to pivot or add
    # columns), you can do so in one of two ways:

    # A. Create a subclass of PandasSerializer, define a function called
    # transform_dataframe(self, dataframe) on the subclass, and assign it to
    # pandas_serializer_class on the view.  You can also use one of the three
    # provided pivoting serializers (see Advanced Usage below).
    #
    # class MyCustomPandasSerializer(PandasSerializer):
    #     def transform_dataframe(self, dataframe):
    #         dataframe.some_pivot_function(in_place=True)
    #         return dataframe
    #
    pandas_serializer_class = MyCustomPandasSerializer

    # B. Alternatively, you can create a custom transform_dataframe function
    # directly on the view.  Again, if no custom transformations are needed,
    # this function does not need to be defined.
    def transform_dataframe(self, dataframe):
        dataframe.some_pivot_function(in_place=True)
        return dataframe

    # NOTE: As the name implies, the primary purpose of transform_dataframe()
    # is to apply a transformation to an existing dataframe.  In PandasView,
    # this dataframe is created by serializing data queried from a Django
    # model.  If you would like to supply your own custom DataFrame from the
    # start (without using a Django model), you can do so with PandasSimpleView
    # as shown in the first example.

    # Step 4. Finally, the provided renderer classes will convert the DataFrame
    # to any of the supported output formats (see above).  By default, all of
    # the formats above are enabled.  To restrict output to only the formats
    # you are interested in, you can define renderer_classes on the view:
    renderer_classes = [PandasCSVRenderer, PandasExcelRenderer]
    # You can also set the default renderers for all of your pandas views by
    # defining the PANDAS_RENDERERS in your settings.py.

    # Step 5 (Optional).  The default filename may not be particularly useful
    # for your users.  To override, define get_pandas_filename() on your view.
    # If a filename is returned, rest_pandas will include the following header:
    # 'Content-Disposition: attachment; filename="Data Export.xlsx"'
    def get_pandas_filename(self, request, format):
        if format in ('xls', 'xlsx'):
            # Use custom filename and Content-Disposition header
            return "Data Export"  # Extension will be appended automatically
        else:
            # Default filename from URL (no Content-Disposition header)
            return None

与现有视图集成

如果您有一个现有的视图集或无法直接继承PandasSimpleViewPandasViewPandasViewSetPandasMixin,您也可以直接集成渲染器和序列化器。最重要的是确保您的序列化器类将Meta.list_serializer_class设置为PandasSerializer或其子类。然后,请确保将Pandas渲染器包含在您的渲染器选项中。请参见#32#36以获取示例。

Django Pandas集成

您也可以让Django Pandas处理查询和生成数据框,而只使用Django REST Pandas进行渲染。

# models.py
from django_pandas.managers import DataFrameManager

class TimeSeries(models.Model):
    # ...
    objects = DataFrameManager()
# views.py
from rest_pandas import PandasSimpleView
from .models import TimeSeries

class TimeSeriesView(PandasSimpleView):
    def get_data(self, request, *args, **kwargs):
        return TimeSeries.objects.to_timeseries(
            index='date',
        )

注册URL

# urls.py
from django.conf.urls import patterns, include, url

from .views import TimeSeriesView
urlpatterns = patterns('',
    url(r'^data', TimeSeriesView.as_view()),
)

# This is only required to support extension-style formats (e.g. /data.csv)
from rest_framework.urlpatterns import format_suffix_patterns
urlpatterns = format_suffix_patterns(urlpatterns)

默认的PandasView将以简单的表格形式提供由提供的模型提供的所有可用数据。如果您正在使用Django REST Framework的ViewSetsRouters,您还可以使用PandasViewSet

自定义渲染器

您可以通过设置settings.py中的PANDAS_RENDERERS或通过在您的单个视图(s)中覆盖renderer_classes来覆盖默认渲染器。PANDAS_RENDERERS的定义与Django REST Framework自己的DEFAULT_RENDERER_CLASSES设置分开,以防您想要在DRP启用视图和常规DRF视图之间混合。

您还可以将DRP渲染器包含在DEFAULT_RENDERER_CLASSES中。在这种情况下,扩展PandasMixin或在您的序列化器上设置list_serializer_class。否则,您可能会收到错误消息,表明序列化器输出不是DataFrame。简而言之,有三种方法可以让DRP渲染器与您的视图一起工作:

  1. 扩展PandasViewPandasSimpleViewPandasViewSet,并使用PANDAS_RENDERERS设置(默认为上面的列表)。
  2. 扩展PandasMixin并自定义REST_FRAMEWORK['DEFAULT_RENDERER_CLASSES']以添加一个或多个rest_pandas渲染器。
  3. 在视图上显式设置renderer_classes,并将Serializer.Meta.list_serializer_class设置为PandasSerializer或其子类。(有关示例,请参阅#32#36。)
class TimeSeriesView(PandasView):
    # renderer_classes default to PANDAS_RENDERERS
    ...

class TimeSeriesView(PandasMixin, ListAPIView):
    # renderer_classes default to REST_FRAMEWORK['DEFAULT_RENDERER_CLASSES']
    ...

日期格式化

默认情况下,Django REST Framework会在渲染器类处理之前将日期序列化为字符串。在许多情况下,您可能希望将日期保留为datetime对象,并让Pandas处理渲染。为此,在您的DRF序列化器上定义一个显式的DateTimeFieldDateField,并将format=None设置

# serializers.py
class TimeSeriesSerializer(serializers.ModelSerializer):
    date = serializers.DateField(format=None)
    class Meta:
        model = TimeSeries
        fields = '__all__'

或者,您可以通过在settings.py中将DATETIME_FORMAT和/或DATE_FORMAT设置为None来全局禁用日期序列化

# settings.py
DATE_FORMAT = None

HTML输出

HTML渲染器提供了创建与数据API具有相同URL的交互式视图的能力。数据框通过to_html()进行处理,然后传递给以下上下文的TemplateHTMLRenderer

上下文变量 描述
表格 to_html()的输出<table>
名称 视图名称
描述 视图描述
URL 当前URL路径(不包含参数)
url_params URL参数
available_formats 允许的扩展名数组(例如'csv''json''xlsx'
wq_chart_type 推荐的图表类型(用于与wq/chartapp.js一起使用,见下文)

TemplateHTMLRenderer一样,模板名称由视图控制。如果您使用DRP与wq框架一起使用,可以利用默认的mustache/rest_pandas.html模板,该模板是为与wq/chartapp.js插件一起使用而设计的。否则,您可能需要提供自定义模板并在视图上设置template_name

如果您需要进行大量自定义,并且实际上不需要将整个数据框以<table>的形式渲染,您始终可以创建另一个视图来处理界面,并使PandasView仅处理API。

注意:为了向后兼容性,如果rest_pandas列在您的已安装应用中,则PandasHTMLRenderer仅包含在默认的PANDAS_RENDERERS中。

构建交互式图表

除了用作数据导出工具外,DRP非常适合创建交互式图表的数据API后端。特别是,DRP可以与d3.jswq/pandas.jswq/chart.js一起使用,以创建交互式的时间序列、散点图和箱线图图表——以及d3.js提供的无限其他图表可能性。

为了方便构建数据API,CSV渲染器是Django REST Pandas的默认设置。虽然pandas JSON序列化器正在改进,但将CSV作为默认设置的主要原因是在序列化时间序列数据时,CSV提供的紧凑性优于JSON。DRP的默认CSV输出将具有单行列标题,这使得它可以直接用于例如d3.csv()。然而,DRP通常与下面的自定义序列化器一起使用,以生成具有嵌套多行列标题的数据框。这对于d3.csv()来说更难解析,但可以很容易地由wq/pandas.js处理,这是d3.js的扩展。

// mychart.js
define(['d3', 'wq/pandas', 'wq/chart'], function(d3, pandas, chart) {

// Unpivoted data (single-row header)
d3.csv("/data.csv", render);

// Pivoted data (multi-row header)
pandas.get('/data.csv', render);

function render(error, data) {
    d3.select('svg')
       .selectAll('rect')
       .data(data)
       // ...
}

});

DRP 包含三个自定义序列化器,它们具有 transform_dataframe() 函数,可以处理常见的用例。这些序列化器类可以通过将它们分配到视图中的 pandas_serializer_class 来使用。如果您使用的是 wq 框架,这些序列化器可以自动利用 DRP 的默认 HTML 模板wq/chartapp.js 一起提供交互式图表。如果您不使用完整的 wq 框架,您仍然可以直接使用 wq/pandas.jswq/chart.js 与这些序列化器的 CSV 输出。

以下示例假设以下数据集用于说明文档。

位置 测量 日期
site1 温度 2016-01-01 3
site1 湿度 2016-01-01 30
site2 温度 2016-01-01 4
site2 温度 2016-01-02 5

PandasUnstackedSerializer

PandasUnstackedSerializer 取消堆叠 数据帧,使一些关键属性列在多行列标题中列出。这使得在不重复每行数据值的情况下包含关于时间序列等元数据变得更加容易。

要指定列标题中使用的属性,在您的 ModelSerializer 子类中定义属性 pandas_unstacked_header。您通常还想要定义 pandas_index,它是一组每个行唯一的元数据字段(例如时间戳)。

# serializers.py
from rest_framework import serializers
from .models import TimeSeries

class TimeSeriesSerializer(serializers.ModelSerializer):
    class Meta:
        model = MultiTimeSeries
        fields = ['date', 'location', 'measurement', 'value']
        pandas_index = ['date']
        pandas_unstacked_header = ['location', 'measurement']

# views.py
from rest_pandas import PandasView, PandasUnstackedSerializer
from .models import TimeSeries
from .serializers import TimeSeriesSerializer

class TimeSeriesView(PandasView):
    queryset = TimeSeries.objects.all()
    serializer_class = TimeSeriesSerializer
    pandas_serializer_class = PandasUnstackedSerializer

使用上述示例数据,此配置将输出以下布局的 CSV 文件

 
位置 site1 site1 site2
测量 温度 湿度 温度
日期  
2016-01-01 3 30 4
2016-01-02 5

然后可以使用 wq/pandas.js 处理成以下结构

[
    {
        "location": "site1",
        "measurement": "temperature",
        "data": [
            {"date": "2016-01-01", "value": 3}
        ]
    },
    {
        "location": "site1",
        "measurement": "humidity",
        "data": [
            {"date": "2016-01-01", "value": 30}
        ]
    },
    {
        "location": "site2",
        "measurement": "temperature",
        "data": [
            {"date": "2016-01-01", "value": 4},
            {"date": "2016-01-02", "value": 5}
        ]
    }
]

PandasUnstackedSerializer 的输出可以与 wq/chart.js 提供的 timeSeries() 图表一起使用

define(['d3', 'wq/pandas', 'wq/chart'], function(d3, pandas, chart) {

var svg = d3.select('svg');
var plot = chart.timeSeries();
pandas.get('/data/timeseries.csv', function(data) {
    svg.datum(data).call(plot);
});

});

PandasScatterSerializer

PandasScatterSerializer 取消堆叠数据帧,并将所选属性组合在一起,以便在 x-y 散点图中更容易地将两个测量值相互对比。

要指定用于坐标名称的属性,在您的 ModelSerializer 子类中定义属性 pandas_scatter_coord。您还可以通过 pandas_scatter_header 指定要包含在标题中的其他元数据属性。您通常还想要定义 pandas_index,它是一组每个行唯一的元数据字段(例如时间戳)。

# serializers.py
from rest_framework import serializers
from .models import TimeSeries

class TimeSeriesSerializer(serializers.ModelSerializer):
    class Meta:
        model = MultiTimeSeries
        fields = ['date', 'location', 'measurement', 'value']
        pandas_index = ['date']
        pandas_scatter_coord = ['measurement']
        pandas_scatter_header = ['location']

# views.py
from rest_pandas import PandasView, PandasScatterSerializer
from .models import TimeSeries
from .serializers import TimeSeriesSerializer

class TimeSeriesView(PandasView):
    queryset = TimeSeries.objects.all()
    serializer_class = TimeSeriesSerializer
    pandas_serializer_class = PandasScatterSerializer

使用上述示例数据,此配置将输出以下布局的 CSV 文件

  温度-值 湿度-值 温度-值
位置 site1 site1 site2
日期  
2014-01-01 3 30 4
2014-01-02 5

然后可以使用 wq/pandas.js 处理成以下结构

[
    {
        "location": "site1",
        "data": [
            {
                "date": "2016-01-01",
                "temperature-value": 3,
                "humidity-value": 30
            }
        ]
    },
    {
        "location": "site2",
        "data": [
            {
                "date": "2016-01-01",
                "temperature-value": 4
            },
            {
                "date": "2016-01-02",
                "temperature-value": 5
            }
        ]
    }
]

PandasScatterSerializer 的输出可以与 wq/chart.js 提供的 scatter() 图表一起使用

define(['d3', 'wq/pandas', 'wq/chart'], function(d3, pandas, chart) {

var svg = d3.select('svg');
var plot = chart.scatter()
    .xvalue(function(d) {
        return d['temperature-value'];
    })
    .yvalue(function(d) {
        return d['humidity-value'];
    });

pandas.get('/data/scatter.csv', function(data) {
    svg.datum(data).call(plot);
});

});

PandasBoxplotSerializer

PandasBoxplotSerializer 通过 matplotlib 的 boxplot_stats 计算箱线图统计信息,并通过未堆叠的数据帧推送结果。可以按指定的组列以及按日期汇总统计信息。

要指定用于组列的属性,在您的 ModelSerializer 子类中定义属性 pandas_boxplot_group。要指定用于基于日期分组的属性,定义 pandas_boxplot_date。您通常还想要定义 pandas_boxplot_header,它将取消任何元数据列的堆叠并从统计信息中排除它们。

# serializers.py
from rest_framework import serializers
from .models import TimeSeries

class TimeSeriesSerializer(serializers.ModelSerializer):
    class Meta:
        model = MultiTimeSeries
        fields = ['date', 'location', 'measurement', 'value']
        pandas_boxplot_group = 'site'
        pandas_boxplot_date = 'date'
        pandas_boxplot_header = ['measurement']

# views.py
from rest_pandas import PandasView, PandasBoxplotSerializer
from .models import TimeSeries
from .serializers import TimeSeriesSerializer

class TimeSeriesView(PandasView):
    queryset = TimeSeries.objects.all()
    serializer_class = TimeSeriesSerializer
    pandas_serializer_class = PandasBoxplotSerializer

使用上述示例数据,此配置将输出与 PandasUnstackedSerializer 相同一般结构的 CSV 文件,但将 value 分散到多个箱线图统计信息列(例如 value-meanvalue-q1value-whishi 等)。可以添加到查询字符串的可选 group` 参数可以在不同的分组之间切换

名称 用途
?group=series 按系列(pandas_boxplot_group)分组
?group=series-year 按系列,然后按年分组
?group=series-month 按系列,然后按月分组
?group=year 按年汇总所有数据
?group=month 按月汇总所有数据

PandasBoxplotSerializer 的输出可以与 wq/chart.js 提供的 boxplot() 图表一起使用

define(['d3', 'wq/pandas', 'wq/chart'], function(d3, pandas, chart) {

var svg = d3.select('svg');
var plot = chart.boxplot();
pandas.get('/data/boxplot.csv?group=year', function(data) {
    svg.datum(data).call(plot);
});

});

项目详情


下载文件

下载适合您平台的文件。如果您不确定选择哪个,请了解更多关于安装包的信息。

源代码分发

rest-pandas-1.1.0.tar.gz (40.7 kB 查看哈希)

上传时间 源代码

构建分发

rest_pandas-1.1.0-py3-none-any.whl (19.5 kB 查看哈希)

上传时间 Python 3