Здесь изучено множество различных решений, но не найдено ни одного, которое работает. Я использую sqlite и pandas для чтения данных из базы данных SQL, но Боке не нравится дата. Я пробовал преобразования в datetime, unixepoch и т. д., И все они, похоже, дают один и тот же результат.
Обновлено: вот полный код:
from os.path import dirname, join
import pandas as pd
import pandas.io.sql as psql
import numpy as np
import sqlite3
import os
from math import pi
from bokeh.plotting import figure, output_file, show
from bokeh.io import output_notebook, curdoc
from bokeh.models import ColumnDataSource, Div, DatetimeTickFormatter
from bokeh.models.widgets import Slider, Select, RadioButtonGroup
from bokeh.layouts import layout, widgetbox
import warnings
import datetime
warnings.filterwarnings('ignore')
## Set up the SQL Connection
conn = sqlite3.connect('/Users/<>/Documents/python_scripts/reptool/reptool_db')
c = conn.cursor()
## Run the SQL
proj = pd.read_sql(
"""
SELECT
CASE WHEN df is null THEN ds ELSE df END AS 'projdate',
CASE WHEN yhat is null THEN y ELSE yhat END AS 'projvol',
strftime('%Y',ds) as 'year'
FROM forecast
LEFT JOIN actuals
ON forecast.ds = actuals.df
""", con=conn)
# HTML index page and inline CSS stylesheet
desc = Div(text=open("/Users/<>/Documents/python_scripts/reptool/description.html").read(), width=800)
## Rename Columns and create list sets
proj.rename(columns = {'projdate': 'x', 'projvol': 'y'}, inplace=True)
x=list(proj['x'])
y=list(proj['y'])
# proj['projdate'] = [datetime.datetime.strptime(x, "%Y-%m-%d").date() for x in proj['projdate']]
# Create input controls
radio_button_group = RadioButtonGroup(
labels=["Actuals", "Forecast","FY Projection"], active=0)
min_year = Slider(title = "Period Start", start=2012, end=2018, value=2013, step=1)
max_year = Slider(title = "Period End", start=2012, end=2018, value=2017, step=1)
## Declare systemic source
source = ColumnDataSource(data=dict(x=[], y=[], year=[]))
## Bokeh tools
TOOLS = "pan,wheel_zoom,box_zoom,reset,xbox_select"
## Set up plot
p = figure(title = "REP Forecast", plot_width=900, plot_height=300, tools=TOOLS, x_axis_label='date', x_axis_type='datetime', y_axis_label='volume', active_drag = "xbox_select")
p.line(x=proj.index, y=y, line_width=2, line_alpha=0.6)
p.xaxis.major_label_orientation = pi/4
# p.xaxis.formatter = DatetimeTickFormatter(seconds=["%Y:%M"],
# minutes=["%Y:%M"],
# minsec=["%Y:%M"],
# hours=["%Y:%M"])
# axis map
# definitions
def select_rep():
selected = proj[
(proj.year >= min_year.value) &
(proj.year >= max_year.value)
]
return selected
def update():
proj = select_rep()
source.data = dict(
year=proj["year"]
)
controls = [min_year, max_year]
for control in controls:
control.on_change('value', lambda attr, old, new: update())
sizing_mode = 'fixed' # 'scale_width' also looks nice with this example
## Build the html page and inline CSS
inputs = widgetbox(*controls)
l = layout([
[desc],
[p],
[inputs],
], )
# update()
curdoc().add_root(l)
curdoc().title = "REP"
Вывод SQLite в Terminal.app выглядит так:
В результате ось x отображается в миллисекундах. Кроме того, ось Y отображается как экспоненциальная запись:
Проблема кажется каким-то образом связана с использованием pandas индексации, и поэтому я не могу здесь сослаться на «x». Я переименовываю столбцы и наборы принудительных списков, которые сами по себе будут правильно печатать ... и поэтому должны правильно отображаться в строке, но, как вы увидите ниже, они этого не делают:
proj.rename(columns = {'projdate': 'x', 'projvol': 'y'}, inplace=True)
x=list(proj['x'])
y=list(proj['y'])
Чтобы линия отображалась в боке, я должен передать ей индекс, потому что передача чего-либо еще, похоже, не приводит к отображению глифа. Итак, сейчас у меня есть это:
p = figure(title = "REP Forecast", plot_width=900, plot_height=300, tools=TOOLS, x_axis_label='date', x_axis_type='datetime', y_axis_label='volume', active_drag = "xbox_select")
p.line(x=proj.index, y=y, line_width=2, line_alpha=0.6)
Пробовал конвертировать в unixepoch в SQL, результат тот же. Пытался преобразовать данные в unixepoch, результат тот же. Пробовал использовать DateTimeTickFormatter, просто показывает все 5-6 лет как один год (думая, что он просто отображает миллисекунды как годы, а не меняет их с миллисекунд на дни.
Я искал здесь и в github, вверх и вниз, и пробовал разные вещи, но в конечном итоге я не могу найти ни одного рабочего примера, где источником является запрос sql, а не csv.
Ни одна из этих вещей не имеет ничего общего с SQL, Bokeh заботится только о данных, которые вы ему предоставляете, а не о том, откуда они пришли. Вы указали, что хотите, чтобы ось даты и времени была на оси x:
x_axis_type='datetime'
Таким образом, Bokeh настроит график с помощью тикера, который выбирает «хорошие» значения на шкале даты и времени, и с помощью модуля форматирования тиков, который отображает местоположения тиков в виде форматированных дат. Однако важно, чтобы координаты данных были в соответствующих единицах, которыми являются миллисекунды с плавающей запятой с эпохи.
Вы можете указать значения x непосредственно в этих единицах измерения, но Bokeh также автоматически преобразует общие типы datetime (например, python stdlib, numpy или pandas) в нужные единицы автоматически. Поэтому проще всего передать столбец значений datetime в качестве значений x в line
.
Чтобы было ясно, это утверждение:
To render the line in Bokeh, it has to use the index
это неверно. Вы можете передать любой столбец фрейма данных, который вам нравится, в качестве значений x, и я предлагаю вам передать столбец даты и времени.
Вы не указали достаточно кода или даже типов столбцов в вашем фрейме даты или данных примера, чтобы сказать больше, чем то, что указано выше. Имея минимальный, полный, воспроизводящий пример, можно было бы диагностировать напрямую.
Я обновил исходный пост, добавив полный код («<>», чтобы скрыть идентификаторы пользователя), снимки экрана вывода SQL и вывода Bokeh. Обратите внимание, что я все еще работаю над ползунками (я ожидаю, что они пока не работают), но график боке - моя текущая проблема.
Что на выходе df.head()
proj.head () дает следующий результат: 2011-08-01 00:00:00 57923.78228 2011
Это строки или фактические даты? Если это строки, их нужно преобразовать в реальные даты. Строки интерпретируются как категориальные значения. Когда я использую SELECT strftime('%Y', foo) as 'bar' FROM table"
с pd.read_sql
на образце набора данных, я вижу строки. Повторяю то, что я сказал ранее: Я предлагаю вам передать столбец с датами.
Поле - datetime, но содержимое, скорее всего, является строкой. Я попытался импортировать их как datetime из Excel, но единственный способ заставить Excel сохранять правильный формат даты и времени при экспорте в CSV - использовать текстовое преобразование .... Я мог либо попробовать то, что вы предлагаете выше, либо попробовать что-нибудь другое преобразование в Excel, если оно когда-нибудь приживется. Позвольте мне попробовать первое (ваше предложение) и посмотреть, к чему это приведет.
См., Например, Вариант parse_dates
в pandas.pydata.org/pandas-docs/stable/generated/…
Я изменил строку SQL на: CASE WHEN df is null THEN strftime('%Y',ds) ELSE strftime('%Y',df) END AS 'projdate',
. А также, повторно импортировав данные, я смог передать дату здесь без использования индекса: p.line(x=x, y=y, line_width=2, line_alpha=0.6)
. Но затем я получил этот странный вывод: ссылка на сайт. Ясно, что он может читать год, но мне нужна полная дата. Я попробую синтаксический анализатор и кое-что еще ... но если у вас есть реальный пример, который работает, это поможет.
Я изменил строку SQL на:
CASE WHEN df is null THEN strftime('%Y',ds) ELSE strftime('%Y',df) END AS 'projdate',
Однако, когда я пытаюсь расширить этот спецификатор до% Y-% m-% d% H-% m-% s, он просто снова читает его как строку.
А также, повторно импортировав данные, я смог передать дату здесь без использования индекса:
p.line(x=x, y=y, line_width=2, line_alpha=0.6)
Но потом я получаю странный результат: ссылка на сайт.
Итак, ясно, что он может читать год, но мне нужно пройти через полную дату, чтобы отобразить прогноз временного ряда. И несмотря на это, он по-прежнему отображает даты и значения y в неправильном масштабе.
Пойду еще немного по этому поводу, но если у кого-то есть другие предложения, я благодарен.
strftime('%Y',ds)
специально извлекает только год, это все, что делает %Y
. Если вам нужна дополнительная информация, вам понадобится другой спецификатор поля strftime
.
Я знаю об этом ... Я потерял фрагмент своего ответа: когда я пытаюсь расширить этот спецификатор до %Y-%m-%d %H-%m-%s
, он просто снова читает его как строку.
Вы установили parse_dates
(ссылка ниже) при звонке на read_sql
? Панды не будут интерпретировать вещи как даты, если вы этого не скажете.
Нет, не видел. Однако я видел ваш другой комментарий. Спасибо ... Я попробую.
parse_dates не работал, но он привел меня к тому, что работает. Я выложу ответ.
РЕШЕНА проблема с датой и временем. Добавлено после запроса SQL:
proj['projdate'] = proj['projdate'].astype('datetime64[ns]')
Что, в свою очередь, дает следующее:
По-прежнему есть проблема с осью x, но поскольку это прямое числовое значение, x_axis_type
должен исправить это.
Пока рабочий код выглядит следующим образом (опять же, все еще повторяется добавление других элементов управления, но все, что касается самого графика Bokeh, работает так, как задумано):
# main.py
# created by: <>
# version: 0.1.2
# created date: 07-Aug-2018
# modified date: 09-Aug-2018
from os.path import dirname, join
import pandas as pd
import pandas.io.sql as psql
import numpy as np
import sqlite3
import os
from math import pi
from bokeh.plotting import figure, output_file, show
from bokeh.io import output_notebook, curdoc
from bokeh.models import ColumnDataSource, Div, DatetimeTickFormatter
from bokeh.models.widgets import Slider, Select, RadioButtonGroup
from bokeh.layouts import layout, widgetbox
import warnings
import datetime
warnings.filterwarnings('ignore')
## Set up the SQL Connection
conn = sqlite3.connect('/Users/<>/Documents/python_scripts/reptool/reptool_db')
c = conn.cursor()
## Run the SQL
proj = pd.read_sql(
"""
SELECT
CASE WHEN df is null THEN strftime('%Y-%m-%d',ds) ELSE strftime('%Y-%m-%d',df) END AS 'projdate',
CASE WHEN yhat is null THEN y ELSE yhat END AS 'projvol',
strftime('%Y',ds) as 'year'
FROM forecast
LEFT JOIN actuals
ON forecast.ds = actuals.df
""", con=conn)
proj['projdate'] = proj['projdate'].astype('datetime64[ns]')
# HTML index page and inline CSS stylesheet
desc = Div(text=open("/Users/<>/Documents/python_scripts/reptool/description.html").read(), width=800)
## Rename Columns and create list sets
proj.rename(columns = {'projdate': 'x', 'projvol': 'y'}, inplace=True)
x=list(proj['x'])
y=list(proj['y'])
# Create input controls
radio_button_group = RadioButtonGroup(
labels=["Actuals", "Forecast","FY Projection"], active=0)
min_year = Slider(title = "Period Start", start=2012, end=2018, value=2013, step=1)
max_year = Slider(title = "Period End", start=2012, end=2018, value=2017, step=1)
## Declare systemic source
source = ColumnDataSource(data=dict(x=[], y=[], year=[]))
## Bokeh tools
TOOLS = "pan,wheel_zoom,box_zoom,reset,xbox_select"
## Set up plot
p = figure(title = "REP Forecast", plot_width=900, plot_height=300, tools=TOOLS, x_axis_label='date', x_axis_type='datetime', y_axis_label='volume', active_drag = "xbox_select")
p.line(x=x, y=y, line_width=2, line_alpha=0.6)
p.xaxis.major_label_orientation = pi/4
# p.xaxis.formatter = DatetimeTickFormatter(seconds=["%Y:%M"],
# minutes=["%Y:%M"],
# minsec=["%Y:%M"],
# hours=["%Y:%M"])
# axis map
# definitions
def select_rep():
selected = proj[
(proj.year >= min_year.value) &
(proj.year >= max_year.value)
]
return selected
def update():
proj = select_rep()
source.data = dict(
year=proj["year"]
)
controls = [min_year, max_year]
for control in controls:
control.on_change('value', lambda attr, old, new: update())
sizing_mode = 'fixed' # 'scale_width' also looks nice with this example
## Build the html page and inline CSS
inputs = widgetbox(*controls)
l = layout([
[desc],
[p],
[inputs],
], )
# update()
curdoc().add_root(l)
curdoc().title = "REP"
Боке не отвечает ни на что, что я ему передаю, кроме proj.index ... Итак, как правильно передать ему значение x? Я пытался следовать каждому примеру, который мог найти, и это не сработало. Но передайте ему proj.index, и он «работает», за исключением того, что шкала времени совершенно нестабильна, и это просто приводит меня к дальнейшим обходным путям ... Если я делаю что-то не так, как правильно, используя SQL, pandas и Боке, что работает?