1
0
mirror of https://github.com/microsoft/qlib.git synced 2026-07-14 08:16:54 +08:00

init commit

This commit is contained in:
Young
2020-09-22 01:43:21 +00:00
parent aa51e5aad3
commit 99ebd87cba
131 changed files with 20218 additions and 0 deletions

View File

@@ -0,0 +1,11 @@
# Copyright (c) Microsoft Corporation.
# Licensed under the MIT License.
GRAPH_NAME_LISt = [
"analysis_position.report_graph",
"analysis_position.score_ic_graph",
"analysis_position.cumulative_return_graph",
"analysis_position.risk_analysis_graph",
"analysis_position.rank_label_graph",
"analysis_model.model_performance_graph",
]

View File

@@ -0,0 +1,4 @@
# Copyright (c) Microsoft Corporation.
# Licensed under the MIT License.
from .analysis_model_performance import model_performance_graph

View File

@@ -0,0 +1,304 @@
# Copyright (c) Microsoft Corporation.
# Licensed under the MIT License.
import pandas as pd
import plotly.tools as tls
import plotly.graph_objs as go
import statsmodels.api as sm
import matplotlib.pyplot as plt
from scipy import stats
from ..graph import ScatterGraph, SubplotsGraph, BarGraph, HeatmapGraph
def _group_return(
pred_label: pd.DataFrame = None, reverse: bool = False, N: int = 5, **kwargs
) -> tuple:
"""
:param pred_label:
:param reverse:
:param N:
:return:
"""
if reverse:
pred_label["score"] *= -1
pred_label = pred_label.sort_values("score", ascending=False)
# Group1 ~ Group5 only consider the dropna values
pred_label_drop = pred_label.dropna(subset=["score"])
# Group
t_df = pd.DataFrame(
{
"Group-%d"
% (i + 1): pred_label_drop.groupby(level="datetime")["label"].apply(
lambda x: x[len(x) // N * i : len(x) // N * (i + 1)].mean()
)
for i in range(N)
}
)
t_df.index = pd.to_datetime(t_df.index)
# Long-Short
t_df["long-short"] = t_df["Group-1"] - t_df["Group-%d" % N]
# Long-Average
t_df["long-average"] = (
t_df["Group-1"] - pred_label.groupby(level="datetime")["label"].mean()
)
t_df = t_df.dropna(how="all") # for days which does not contain label
# FIXME: support HIGH-FREQ
t_df.index = t_df.index.strftime("%Y-%m-%d")
# Cumulative Return By Group
group_scatter_figure = ScatterGraph(
t_df.cumsum(),
layout=dict(
title="Cumulative Return", xaxis=dict(type="category", tickangle=45)
),
).figure
t_df = t_df.loc[:, ["long-short", "long-average"]]
_bin_size = ((t_df.max() - t_df.min()) / 20).min()
group_hist_figure = SubplotsGraph(
t_df,
kind_map=dict(kind="DistplotGraph", kwargs=dict(bin_size=_bin_size)),
subplots_kwargs=dict(
rows=1,
cols=2,
print_grid=False,
subplot_titles=["long-short", "long-average"],
),
).figure
return group_scatter_figure, group_hist_figure
def _plot_qq(data: pd.Series = None, dist=stats.norm) -> go.Figure:
"""
:param data:
:param dist:
:return:
"""
fig, ax = plt.subplots(figsize=(8, 5))
_mpl_fig = sm.qqplot(data.dropna(), dist, fit=True, line="45", ax=ax)
return tls.mpl_to_plotly(_mpl_fig)
def _pred_ic(pred_label: pd.DataFrame = None, rank: bool = False, **kwargs) -> tuple:
"""
:param pred_label:
:param rank:
:return:
"""
if rank:
ic = pred_label.groupby(level="datetime").apply(
lambda x: x["label"].rank(pct=True).corr(x["score"].rank(pct=True))
)
else:
ic = pred_label.groupby(level="datetime").apply(
lambda x: x["label"].corr(x["score"])
)
_index = (
ic.index.get_level_values(0).astype("str").str.replace("-", "").str.slice(0, 6)
)
_monthly_ic = ic.groupby(_index).mean()
_monthly_ic.index = pd.MultiIndex.from_arrays(
[_monthly_ic.index.str.slice(0, 4), _monthly_ic.index.str.slice(4, 6)],
names=["year", "month"],
)
# fill month
_month_list = pd.date_range(
start=pd.Timestamp(f"{_index.min()[:4]}0101"),
end=pd.Timestamp(f"{_index.max()[:4]}1231"),
freq="1M",
)
_years = []
_month = []
for _date in _month_list:
_date = _date.strftime("%Y%m%d")
_years.append(_date[:4])
_month.append(_date[4:6])
fill_index = pd.MultiIndex.from_arrays([_years, _month], names=["year", "month"])
_monthly_ic = _monthly_ic.reindex(fill_index)
_ic_df = ic.to_frame("ic")
ic_bar_figure = ic_figure(_ic_df, kwargs.get("show_nature_day", True))
ic_heatmap_figure = HeatmapGraph(
_monthly_ic.unstack(),
layout=dict(title="Monthly IC", yaxis=dict(tickformat=",d")),
graph_kwargs=dict(xtype="array", ytype="array"),
).figure
dist = stats.norm
_qqplot_fig = _plot_qq(ic, dist)
if isinstance(dist, stats.norm.__class__):
dist_name = "Normal"
else:
dist_name = "Unknown"
_bin_size = ((_ic_df.max() - _ic_df.min()) / 20).min()
_sub_graph_data = [
(
"ic",
dict(
row=1,
col=1,
name="",
kind="DistplotGraph",
graph_kwargs=dict(bin_size=_bin_size),
),
),
(_qqplot_fig, dict(row=1, col=2)),
]
ic_hist_figure = SubplotsGraph(
_ic_df.dropna(),
kind_map=dict(kind="HistogramGraph", kwargs=dict()),
subplots_kwargs=dict(
rows=1,
cols=2,
print_grid=False,
subplot_titles=["IC", "IC %s Dist. Q-Q" % dist_name],
),
sub_graph_data=_sub_graph_data,
layout=dict(
yaxis2=dict(title="Observed Quantile"),
xaxis2=dict(title=f"{dist_name} Distribution Quantile"),
),
).figure
return ic_bar_figure, ic_heatmap_figure, ic_hist_figure
def _pred_autocorr(pred_label: pd.DataFrame, lag=1, **kwargs) -> tuple:
pred = pred_label.copy()
pred["score_last"] = pred.groupby(level="instrument")["score"].shift(lag)
ac = pred.groupby(level="datetime").apply(
lambda x: x["score"].rank(pct=True).corr(x["score_last"].rank(pct=True))
)
# FIXME: support HIGH-FREQ
_df = ac.to_frame("value")
_df.index = _df.index.strftime("%Y-%m-%d")
ac_figure = ScatterGraph(
_df,
layout=dict(
title="Auto Correlation", xaxis=dict(type="category", tickangle=45)
),
).figure
return (ac_figure,)
def _pred_turnover(pred_label: pd.DataFrame, N=5, lag=1, **kwargs) -> tuple:
pred = pred_label.copy()
pred["score_last"] = pred.groupby(level="instrument")["score"].shift(lag)
top = pred.groupby(level="datetime").apply(
lambda x: 1
- x.nlargest(len(x) // N, columns="score")
.index.isin(x.nlargest(len(x) // N, columns="score_last").index)
.sum()
/ (len(x) // N)
)
bottom = pred.groupby(level="datetime").apply(
lambda x: 1
- x.nsmallest(len(x) // N, columns="score")
.index.isin(x.nsmallest(len(x) // N, columns="score_last").index)
.sum()
/ (len(x) // N)
)
r_df = pd.DataFrame({"Top": top, "Bottom": bottom,})
# FIXME: support HIGH-FREQ
r_df.index = r_df.index.strftime("%Y-%m-%d")
turnover_figure = ScatterGraph(
r_df,
layout=dict(
title="Top-Bottom Turnover", xaxis=dict(type="category", tickangle=45)
),
).figure
return (turnover_figure,)
def ic_figure(ic_df: pd.DataFrame, show_nature_day=True, **kwargs) -> go.Figure:
"""IC figure
:param ic_df: ic DataFrame
:param show_nature_day: whether to display the abscissa of non-trading day
:return: plotly.graph_objs.Figure
"""
if show_nature_day:
date_index = pd.date_range(ic_df.index.min(), ic_df.index.max())
ic_df = ic_df.reindex(date_index)
# FIXME: support HIGH-FREQ
ic_df.index = ic_df.index.strftime("%Y-%m-%d")
ic_bar_figure = BarGraph(
ic_df,
layout=dict(
title="Information Coefficient (IC)",
xaxis=dict(type="category", tickangle=45),
),
).figure
return ic_bar_figure
def model_performance_graph(
pred_label: pd.DataFrame,
lag: int = 1,
N: int = 5,
reverse=False,
rank=False,
graph_names: list = ["group_return", "pred_ic", "pred_autocorr"],
show_notebook: bool = True,
show_nature_day=True,
) -> [list, tuple]:
"""Model performance
:param pred_label: index is **pd.MultiIndex**, index name is **[instrument, datetime]**; columns names is **[score, label]**
.. code-block:: python
instrument datetime score label
SH600004 2017-12-11 -0.013502 -0.013502
2017-12-12 -0.072367 -0.072367
2017-12-13 -0.068605 -0.068605
2017-12-14 0.012440 0.012440
2017-12-15 -0.102778 -0.102778
:param lag: `pred.groupby(level='instrument')['score'].shift(lag)`. It will be only used in the auto-correlation computing.
:param N: group number, default 5
:param reverse: if `True`, `pred['score'] *= -1`
:param rank: if **True**, calculate rank ic
:param graph_names: graph names; default ['cumulative_return', 'pred_ic', 'pred_autocorr', 'pred_turnover']
:param show_notebook: whether to display graphics in notebook, the default is `True`
:param show_nature_day: whether to display the abscissa of non-trading day
:return: if show_notebook is True, display in notebook; else return `plotly.graph_objs.Figure` list
"""
figure_list = []
for graph_name in graph_names:
fun_res = eval(f"_{graph_name}")(
pred_label=pred_label,
lag=lag,
N=N,
reverse=reverse,
rank=rank,
show_nature_day=show_nature_day,
)
figure_list += fun_res
if show_notebook:
BarGraph.show_graph_in_notebook(figure_list)
else:
return figure_list

View File

@@ -0,0 +1,8 @@
# Copyright (c) Microsoft Corporation.
# Licensed under the MIT License.
from .cumulative_return import cumulative_return_graph
from .score_ic import score_ic_graph
from .report import report_graph
from .rank_label import rank_label_graph
from .risk_analysis import risk_analysis_graph

View File

@@ -0,0 +1,281 @@
# Copyright (c) Microsoft Corporation.
# Licensed under the MIT License.
import copy
from typing import Iterable
import pandas as pd
import plotly.graph_objs as go
from ..graph import BaseGraph, SubplotsGraph
from ..analysis_position.parse_position import get_position_data
def _get_cum_return_data_with_position(
position: dict,
report_normal: pd.DataFrame,
label_data: pd.DataFrame,
start_date=None,
end_date=None,
):
"""
:param position:
:param report_normal:
:param label_data:
:param start_date:
:param end_date:
:return:
"""
_cumulative_return_df = get_position_data(
position=position,
report_normal=report_normal,
label_data=label_data,
start_date=start_date,
end_date=end_date,
).copy()
_cumulative_return_df["label"] = (
_cumulative_return_df["label"] - _cumulative_return_df["bench"]
)
_cumulative_return_df = _cumulative_return_df.dropna()
df_gp = _cumulative_return_df.groupby(level="datetime")
result_list = []
for gp in df_gp:
date = gp[0]
day_df = gp[1]
_hold_df = day_df[day_df["status"] == 0]
_buy_df = day_df[day_df["status"] == 1]
_sell_df = day_df[day_df["status"] == -1]
hold_value = (_hold_df["label"] * _hold_df["weight"]).sum()
hold_weight = _hold_df["weight"].sum()
hold_mean = (hold_value / hold_weight) if hold_weight else 0
sell_value = (_sell_df["label"] * _sell_df["weight"]).sum()
sell_weight = _sell_df["weight"].sum()
sell_mean = (sell_value / sell_weight) if sell_weight else 0
buy_value = (_buy_df["label"] * _buy_df["weight"]).sum()
buy_weight = _buy_df["weight"].sum()
buy_mean = (buy_value / buy_weight) if buy_weight else 0
result_list.append(
dict(
hold_value=hold_value,
hold_mean=hold_mean,
hold_weight=hold_weight,
buy_value=buy_value,
buy_mean=buy_mean,
buy_weight=buy_weight,
sell_value=sell_value,
sell_mean=sell_mean,
sell_weight=sell_weight,
buy_minus_sell_value=buy_value - sell_value,
buy_minus_sell_mean=buy_mean - sell_mean,
buy_plus_sell_weight=buy_weight + sell_weight,
date=date,
)
)
r_df = pd.DataFrame(data=result_list)
r_df["cum_hold"] = r_df["hold_mean"].cumsum()
r_df["cum_buy"] = r_df["buy_mean"].cumsum()
r_df["cum_sell"] = r_df["sell_mean"].cumsum()
r_df["cum_buy_minus_sell"] = r_df["buy_minus_sell_mean"].cumsum()
return r_df
def _get_figure_with_position(
position: dict,
report_normal: pd.DataFrame,
label_data: pd.DataFrame,
start_date=None,
end_date=None,
) -> Iterable[go.Figure]:
"""Get average analysis figures
:param position: position
:param report_normal:
:param label_data:
:param start_date:
:param end_date:
:return:
"""
cum_return_df = _get_cum_return_data_with_position(
position, report_normal, label_data, start_date, end_date
)
cum_return_df = cum_return_df.set_index("date")
# FIXME: support HIGH-FREQ
cum_return_df.index = cum_return_df.index.strftime('%Y-%m-%d')
# Create figures
for _t_name in ["buy", "sell", "buy_minus_sell", "hold"]:
sub_graph_data = [
(
"cum_{}".format(_t_name),
dict(
row=1, col=1, graph_kwargs={"mode": "lines+markers", "xaxis": "x3"}
),
),
(
"{}_weight".format(
_t_name.replace("minus", "plus") if "minus" in _t_name else _t_name
),
dict(row=2, col=1),
),
(
"{}_value".format(_t_name),
dict(row=1, col=2, kind="HistogramGraph", graph_kwargs={}),
),
]
_default_xaxis = dict(showline=False, zeroline=True, tickangle=45)
_default_yaxis = dict(zeroline=True, showline=True, showticklabels=True)
sub_graph_layout = dict(
xaxis1=dict(**_default_xaxis, type="category", showticklabels=False),
xaxis3=dict(**_default_xaxis, type="category"),
xaxis2=_default_xaxis,
yaxis1=dict(**_default_yaxis, title=_t_name),
yaxis2=_default_yaxis,
yaxis3=_default_yaxis,
)
mean_value = cum_return_df["{}_value".format(_t_name)].mean()
layout = dict(
height=500,
title=f"{_t_name}(the red line in the histogram on the right represents the average)",
shapes=[
{
"type": "line",
"xref": "x2",
"yref": "paper",
"x0": mean_value,
"y0": 0,
"x1": mean_value,
"y1": 1,
# NOTE: 'fillcolor': '#d3d3d3', 'opacity': 0.3,
"line": {"color": "red", "width": 1},
},
],
)
kind_map = dict(kind="ScatterGraph", kwargs=dict(mode="lines+markers"))
specs = [
[{"rowspan": 1}, {"rowspan": 2}],
[{"rowspan": 1}, None],
]
subplots_kwargs = dict(
vertical_spacing=0.01,
rows=2,
cols=2,
row_width=[1, 2],
column_width=[3, 1],
print_grid=False,
specs=specs,
)
yield SubplotsGraph(
cum_return_df,
layout=layout,
kind_map=kind_map,
sub_graph_layout=sub_graph_layout,
sub_graph_data=sub_graph_data,
subplots_kwargs=subplots_kwargs,
).figure
def cumulative_return_graph(
position: dict,
report_normal: pd.DataFrame,
label_data: pd.DataFrame,
show_notebook=True,
start_date=None,
end_date=None,
) -> Iterable[go.Figure]:
"""Backtest buy, sell, and holding cumulative return graph
Example:
.. code-block:: python
from qlib.data import D
from qlib.contrib.evaluate import risk_analysis, backtest, long_short_backtest
from qlib.contrib.strategy import TopkDropoutStrategy
# backtest parameters
bparas = {}
bparas['limit_threshold'] = 0.095
bparas['account'] = 1000000000
sparas = {}
sparas['topk'] = 50
sparas['n_drop'] = 5
strategy = TopkDropoutStrategy(**sparas)
report_normal_df, positions = backtest(pred_df, strategy, **bparas)
pred_df_dates = pred_df.index.get_level_values(level='datetime')
features_df = D.features(D.instruments('csi500'), ['Ref($close, -1)/$close - 1'], pred_df_dates.min(), pred_df_dates.max())
features_df.columns = ['label']
qcr.cumulative_return_graph(positions, report_normal_df, features_df)
Graph desc:
- Axis X: Trading day
- Axis Y:
- Above axis Y: (((Ref($close, -1)/$close - 1) * weight).sum() / weight.sum()).cumsum()
- Below axis Y: Daily weight sum
- In the sell graph, y < 0 stands for profit; in other cases, y > 0 stands for profit.
- In the buy_minus_sell graph, the y value of the weight graph at the bottom is buy_weight + sell_weight.
- In each graph, the red line in the histogram on the right represents the average.
:param position: position data
:param report_normal:
.. code-block:: python
return cost bench turnover
date
2017-01-04 0.003421 0.000864 0.011693 0.576325
2017-01-05 0.000508 0.000447 0.000721 0.227882
2017-01-06 -0.003321 0.000212 -0.004322 0.102765
2017-01-09 0.006753 0.000212 0.006874 0.105864
2017-01-10 -0.000416 0.000440 -0.003350 0.208396
:param label_data: `D.features` result; index is `pd.MultiIndex`, index name is [`instrument`, `datetime`]; columns names is [`label`].
**The ``label`` T is the change from T to T+1**, it is recommended to use ``close``, example: D.features(D.instruments('csi500'), ['Ref($close, -1)/$close-1'])
.. code-block:: python
label
instrument datetime
SH600004 2017-12-11 -0.013502
2017-12-12 -0.072367
2017-12-13 -0.068605
2017-12-14 0.012440
2017-12-15 -0.102778
:param show_notebook: True or False. If True, show graph in notebook, else return figures
:param start_date: start date
:param end_date: end date
:return:
"""
position = copy.deepcopy(position)
report_normal = report_normal.copy()
label_data.columns = ["label"]
_figures = _get_figure_with_position(
position, report_normal, label_data, start_date, end_date
)
if show_notebook:
BaseGraph.show_graph_in_notebook(_figures)
else:
return _figures

View File

@@ -0,0 +1,187 @@
# Copyright (c) Microsoft Corporation.
# Licensed under the MIT License.
import pandas as pd
from ...backtest.profit_attribution import get_stock_weight_df
def parse_position(position: dict = None) -> pd.DataFrame:
"""Parse position dict to position DataFrame
:param position: position data
:return: position DataFrame;
.. code-block:: python
position_df = parse_position(positions)
print(position_df.head())
# status: 0-hold, -1-sell, 1-buy
amount cash count price status weight
instrument datetime
SZ000547 2017-01-04 44.154290 211405.285654 1 205.189575 1 0.031255
SZ300202 2017-01-04 60.638845 211405.285654 1 154.356506 1 0.032290
SH600158 2017-01-04 46.531681 211405.285654 1 153.895142 1 0.024704
SH600545 2017-01-04 197.173093 211405.285654 1 48.607037 1 0.033063
SZ000930 2017-01-04 103.938300 211405.285654 1 80.759453 1 0.028958
"""
position_weight_df = get_stock_weight_df(position)
# If the day does not exist, use the last weight
position_weight_df.fillna(method="ffill", inplace=True)
previous_data = {"date": None, "code_list": []}
result_df = pd.DataFrame()
for _trading_date, _value in position.items():
# pd_date type: pd.Timestamp
_cash = _value.pop("cash")
for _item in ["today_account_value"]:
if _item in _value:
_value.pop(_item)
_trading_day_df = pd.DataFrame.from_dict(_value, orient="index")
_trading_day_df["weight"] = position_weight_df.loc[_trading_date]
_trading_day_df["cash"] = _cash
_trading_day_df["date"] = _trading_date
# status: 0-hold, -1-sell, 1-buy
_trading_day_df["status"] = 0
# T not exist, T-1 exist, T sell
_cur_day_sell = set(previous_data["code_list"]) - set(_trading_day_df.index)
# T exist, T-1 not exist, T buy
_cur_day_buy = set(_trading_day_df.index) - set(previous_data["code_list"])
# Trading day buy
_trading_day_df.loc[_trading_day_df.index.isin(_cur_day_buy), "status"] = 1
# Trading day sell
if not result_df.empty:
_trading_day_sell_df = result_df.loc[
(result_df["date"] == previous_data["date"])
& (result_df.index.isin(_cur_day_sell))
].copy()
if not _trading_day_sell_df.empty:
_trading_day_sell_df["status"] = -1
_trading_day_sell_df["date"] = _trading_date
_trading_day_df = _trading_day_df.append(
_trading_day_sell_df, sort=False
)
result_df = result_df.append(_trading_day_df, sort=True)
previous_data = dict(
date=_trading_date,
code_list=_trading_day_df[_trading_day_df["status"] != -1].index,
)
result_df.reset_index(inplace=True)
result_df.rename(columns={"date": "datetime", "index": "instrument"}, inplace=True)
return result_df.set_index(["instrument", "datetime"])
def _add_label_to_position(
position_df: pd.DataFrame, label_data: pd.DataFrame
) -> pd.DataFrame:
"""Concat position with custom label
:param position_df: position DataFrame
:param label_data:
:return: concat result
"""
_start_time = position_df.index.get_level_values(level="datetime").min()
_end_time = position_df.index.get_level_values(level="datetime").max()
label_data = label_data.loc(axis=0)[:, pd.to_datetime(_start_time) :]
_result_df = pd.concat([position_df, label_data], axis=1, sort=True).reindex(
label_data.index
)
_result_df = _result_df.loc[_result_df.index.get_level_values(1) <= _end_time]
return _result_df
def _add_bench_to_position(
position_df: pd.DataFrame = None, bench: pd.Series = None
) -> pd.DataFrame:
"""Concat position with bench
:param position_df: position DataFrame
:param bench: report normal data
:return: concat result
"""
_temp_df = position_df.reset_index(level="instrument")
# FIXME: After the stock is bought and sold, the rise and fall of the next trading day are calculated.
_temp_df["bench"] = bench.shift(-1)
res_df = _temp_df.set_index(["instrument", _temp_df.index])
return res_df
def _calculate_label_rank(df: pd.DataFrame) -> pd.DataFrame:
"""calculate label rank
:param df:
:return:
"""
_label_name = "label"
def _calculate_day_value(g_df: pd.DataFrame):
g_df = g_df.copy()
g_df["rank_ratio"] = g_df[_label_name].rank(ascending=False) / len(g_df) * 100
# Sell: -1, Hold: 0, Buy: 1
for i in [-1, 0, 1]:
g_df.loc[g_df["status"] == i, "rank_label_mean"] = g_df[
g_df["status"] == i
]["rank_ratio"].mean()
g_df["excess_return"] = g_df[_label_name] - g_df[_label_name].mean()
return g_df
return df.groupby(level="datetime").apply(_calculate_day_value)
def get_position_data(
position: dict,
label_data: pd.DataFrame,
report_normal: pd.DataFrame = None,
calculate_label_rank=False,
start_date=None,
end_date=None,
) -> pd.DataFrame:
"""Concat position data with pred/report_normal
:param position: position data
:param report_normal: report normal, must be container 'bench' column
:param label_data:
:param calculate_label_rank:
:param start_date: start date
:param end_date: end date
:return: concat result,
columns: ['amount', 'cash', 'count', 'price', 'status', 'weight', 'label',
'rank_ratio', 'rank_label_mean', 'excess_return', 'score', 'bench']
index: ['instrument', 'date']
"""
_position_df = parse_position(position)
# Add custom_label, rank_ratio, rank_mean, and excess_return field
_position_df = _add_label_to_position(_position_df, label_data)
if calculate_label_rank:
_position_df = _calculate_label_rank(_position_df)
if report_normal is not None:
# Add bench field
_position_df = _add_bench_to_position(_position_df, report_normal["bench"])
_date_list = _position_df.index.get_level_values(level="datetime")
start_date = _date_list.min() if start_date is None else start_date
end_date = _date_list.max() if end_date is None else end_date
_position_df = _position_df.loc[
(start_date <= _date_list) & (_date_list <= end_date)
]
return _position_df

View File

@@ -0,0 +1,127 @@
# Copyright (c) Microsoft Corporation.
# Licensed under the MIT License.
import copy
from typing import Iterable
import pandas as pd
import plotly.graph_objs as go
from ..graph import ScatterGraph
from ..analysis_position.parse_position import get_position_data
def _get_figure_with_position(
position: dict, label_data: pd.DataFrame, start_date=None, end_date=None
) -> Iterable[go.Figure]:
"""Get average analysis figures
:param position: position
:param label_data:
:param start_date:
:param end_date:
:return:
"""
_position_df = get_position_data(
position,
label_data,
calculate_label_rank=True,
start_date=start_date,
end_date=end_date,
)
res_dict = dict()
_pos_gp = _position_df.groupby(level=1)
for _item in _pos_gp:
_date = _item[0]
_day_df = _item[1]
_day_value = res_dict.setdefault(_date, {})
for _i, _name in {0: "Hold", 1: "Buy", -1: "Sell"}.items():
_temp_df = _day_df[_day_df["status"] == _i]
if _temp_df.empty:
_day_value[_name] = 0
else:
_day_value[_name] = _temp_df["rank_label_mean"].values[0]
_res_df = pd.DataFrame.from_dict(res_dict, orient="index")
# FIXME: support HIGH-FREQ
_res_df.index = _res_df.index.strftime('%Y-%m-%d')
for _col in _res_df.columns:
yield ScatterGraph(
_res_df.loc[:, [_col]],
layout=dict(
title=_col,
xaxis=dict(type="category", tickangle=45),
yaxis=dict(title="lable-rank-ratio: %"),
),
graph_kwargs=dict(mode="lines+markers"),
).figure
def rank_label_graph(
position: dict,
label_data: pd.DataFrame,
start_date=None,
end_date=None,
show_notebook=True,
) -> Iterable[go.Figure]:
"""Ranking percentage of stocks buy, sell, and holding on the trading day.
Average rank-ratio(similar to **sell_df['label'].rank(ascending=False) / len(sell_df)**) of daily trading
Example:
.. code-block:: python
from qlib.data import D
from qlib.contrib.evaluate import backtest
from qlib.contrib.strategy import TopkDropoutStrategy
# backtest parameters
bparas = {}
bparas['limit_threshold'] = 0.095
bparas['account'] = 1000000000
sparas = {}
sparas['topk'] = 50
sparas['n_drop'] = 230
strategy = TopkDropoutStrategy(**sparas)
_, positions = backtest(pred_df, strategy, **bparas)
pred_df_dates = pred_df.index.get_level_values(level='datetime')
features_df = D.features(D.instruments('csi500'), ['Ref($close, -1)/$close-1'], pred_df_dates.min(), pred_df_dates.max())
features_df.columns = ['label']
qcr.rank_label_graph(positions, features_df, pred_df_dates.min(), pred_df_dates.max())
:param position: position data; **qlib.contrib.backtest.backtest.backtest** result
:param label_data: **D.features** result; index is **pd.MultiIndex**, index name is **[instrument, datetime]**; columns names is **[label]**.
**The ``label`` T is the change from T to T+1**, it is recommended to use ``close``, example: D.features(D.instruments('csi500'), ['Ref($close, -1)/$close-1'])
.. code-block:: python
label
instrument datetime
SH600004 2017-12-11 -0.013502
2017-12-12 -0.072367
2017-12-13 -0.068605
2017-12-14 0.012440
2017-12-15 -0.102778
:param start_date: start date
:param end_date: end_date
:param show_notebook: **True** or **False**. If True, show graph in notebook, else return figures
:return:
"""
position = copy.deepcopy(position)
label_data.columns = ["label"]
_figures = _get_figure_with_position(position, label_data, start_date, end_date)
if show_notebook:
ScatterGraph.show_graph_in_notebook(_figures)
else:
return _figures

View File

@@ -0,0 +1,220 @@
# Copyright (c) Microsoft Corporation.
# Licensed under the MIT License.
import pandas as pd
from ..graph import SubplotsGraph, BaseGraph
def _calculate_maximum(df: pd.DataFrame, is_ex: bool = False):
"""
:param df:
:param is_ex:
:return:
"""
if is_ex:
end_date = df["cum_ex_return_wo_cost_mdd"].idxmin()
start_date = df.loc[df.index <= end_date]["cum_ex_return_wo_cost"].idxmax()
else:
end_date = df["return_wo_mdd"].idxmin()
start_date = df.loc[df.index <= end_date]["cum_return_wo_cost"].idxmax()
return start_date, end_date
def _calculate_mdd(series):
"""
Calculate mdd
:param series:
:return:
"""
return series - series.cummax()
def _calculate_report_data(df: pd.DataFrame) -> pd.DataFrame:
"""
:param df:
:return:
"""
df.index = df.index.strftime("%Y-%m-%d")
report_df = pd.DataFrame()
report_df["cum_bench"] = df["bench"].cumsum()
report_df["cum_return_wo_cost"] = df["return"].cumsum()
report_df["cum_return_w_cost"] = (df["return"] - df["cost"]).cumsum()
# report_df['cum_return'] - report_df['cum_return'].cummax()
report_df["return_wo_mdd"] = _calculate_mdd(report_df["cum_return_wo_cost"])
report_df["return_w_cost_mdd"] = _calculate_mdd(
(df["return"] - df["cost"]).cumsum()
)
report_df["cum_ex_return_wo_cost"] = (df["return"] - df["bench"]).cumsum()
report_df["cum_ex_return_w_cost"] = (
df["return"] - df["bench"] - df["cost"]
).cumsum()
report_df["cum_ex_return_wo_cost_mdd"] = _calculate_mdd(
(df["return"] - df["bench"]).cumsum()
)
report_df["cum_ex_return_w_cost_mdd"] = _calculate_mdd(
(df["return"] - df["cost"] - df["bench"]).cumsum()
)
# return_wo_mdd , return_w_cost_mdd, cum_ex_return_wo_cost_mdd, cum_ex_return_w
report_df["turnover"] = df["turnover"]
report_df.sort_index(ascending=True, inplace=True)
return report_df
def _report_figure(df: pd.DataFrame) -> [list, tuple]:
"""
:param df:
:return:
"""
# Get data
report_df = _calculate_report_data(df)
# Maximum Drawdown
max_start_date, max_end_date = _calculate_maximum(report_df)
ex_max_start_date, ex_max_end_date = _calculate_maximum(report_df, True)
_temp_df = report_df.reset_index()
_temp_df.loc[-1] = 0
_temp_df = _temp_df.shift(1)
_temp_df.loc[0, "index"] = "T0"
_temp_df.set_index("index", inplace=True)
_temp_df.iloc[0] = 0
report_df = _temp_df
# Create figure
_default_kind_map = dict(kind="ScatterGraph", kwargs={"mode": "lines+markers"})
_temp_fill_args = {"fill": "tozeroy", "mode": "lines+markers"}
_column_row_col_dict = [
("cum_bench", dict(row=1, col=1)),
("cum_return_wo_cost", dict(row=1, col=1)),
("cum_return_w_cost", dict(row=1, col=1)),
("return_wo_mdd", dict(row=2, col=1, graph_kwargs=_temp_fill_args)),
("return_w_cost_mdd", dict(row=3, col=1, graph_kwargs=_temp_fill_args)),
("cum_ex_return_wo_cost", dict(row=4, col=1)),
("cum_ex_return_w_cost", dict(row=4, col=1)),
("turnover", dict(row=5, col=1)),
("cum_ex_return_w_cost_mdd", dict(row=6, col=1, graph_kwargs=_temp_fill_args)),
("cum_ex_return_wo_cost_mdd", dict(row=7, col=1, graph_kwargs=_temp_fill_args)),
]
_subplot_layout = dict(
xaxis=dict(showline=True, type="category", tickangle=45),
yaxis=dict(zeroline=True, showline=True, showticklabels=True),
)
for i in range(2, 8):
# yaxis
_subplot_layout.update(
{
"yaxis{}".format(i): dict(
zeroline=True, showline=True, showticklabels=True
)
}
)
_layout_style = dict(
height=1200,
title=" ",
shapes=[
{
"type": "rect",
"xref": "x",
"yref": "paper",
"x0": max_start_date,
"y0": 0.55,
"x1": max_end_date,
"y1": 1,
"fillcolor": "#d3d3d3",
"opacity": 0.3,
"line": {"width": 0,},
},
{
"type": "rect",
"xref": "x",
"yref": "paper",
"x0": ex_max_start_date,
"y0": 0,
"x1": ex_max_end_date,
"y1": 0.55,
"fillcolor": "#d3d3d3",
"opacity": 0.3,
"line": {"width": 0,},
},
],
)
_subplot_kwargs = dict(
shared_xaxes=True,
vertical_spacing=0.01,
rows=7,
cols=1,
row_width=[1, 1, 1, 3, 1, 1, 3],
print_grid=False,
)
figure = SubplotsGraph(
df=report_df,
layout=_layout_style,
sub_graph_data=_column_row_col_dict,
subplots_kwargs=_subplot_kwargs,
kind_map=_default_kind_map,
sub_graph_layout=_subplot_layout,
).figure
return (figure,)
def report_graph(report_df: pd.DataFrame, show_notebook: bool = True) -> [list, tuple]:
"""display backtest report
Example:
.. code-block:: python
from qlib.contrib.evaluate import backtest
from qlib.contrib.strategy import TopkDropoutStrategy
# backtest parameters
bparas = {}
bparas['limit_threshold'] = 0.095
bparas['account'] = 1000000000
sparas = {}
sparas['topk'] = 50
sparas['n_drop'] = 230
strategy = TopkDropoutStrategy(**sparas)
report_normal_df, _ = backtest(pred_df, strategy, **bparas)
qcr.report_graph(report_normal_df)
:param report_df: **df.index.name** must be **date**, **df.columns** must contain **return**, **turnover**, **cost**, **bench**
.. code-block:: python
return cost bench turnover
date
2017-01-04 0.003421 0.000864 0.011693 0.576325
2017-01-05 0.000508 0.000447 0.000721 0.227882
2017-01-06 -0.003321 0.000212 -0.004322 0.102765
2017-01-09 0.006753 0.000212 0.006874 0.105864
2017-01-10 -0.000416 0.000440 -0.003350 0.208396
:param show_notebook: whether to display graphics in notebook, the default is **True**
:return: if show_notebook is True, display in notebook; else return **plotly.graph_objs.Figure** list
"""
report_df = report_df.copy()
fig_list = _report_figure(report_df)
if show_notebook:
BaseGraph.show_graph_in_notebook(fig_list)
else:
return fig_list

View File

@@ -0,0 +1,271 @@
# Copyright (c) Microsoft Corporation.
# Licensed under the MIT License.
from typing import Iterable
import pandas as pd
import plotly.graph_objs as py
from ...evaluate import risk_analysis
from ..graph import SubplotsGraph, ScatterGraph
def _get_risk_analysis_data_with_report(
report_normal_df: pd.DataFrame,
# report_long_short_df: pd.DataFrame,
date: pd.Timestamp,
) -> pd.DataFrame:
"""Get risk analysis data with report
:param report_normal_df: report data
:param report_long_short_df: report data
:param date: date string
:return:
"""
analysis = dict()
# if not report_long_short_df.empty:
# analysis["pred_long"] = risk_analysis(report_long_short_df["long"])
# analysis["pred_short"] = risk_analysis(report_long_short_df["short"])
# analysis["pred_long_short"] = risk_analysis(report_long_short_df["long_short"])
if not report_normal_df.empty:
analysis["sub_bench"] = risk_analysis(
report_normal_df["return"] - report_normal_df["bench"]
)
analysis["sub_cost"] = risk_analysis(
report_normal_df["return"]
- report_normal_df["bench"]
- report_normal_df["cost"]
)
analysis_df = pd.concat(analysis) # type: pd.DataFrame
analysis_df["date"] = date
return analysis_df
def _get_all_risk_analysis(risk_df: pd.DataFrame) -> pd.DataFrame:
"""risk_df to standard
:param risk_df: risk data
:return:
"""
if risk_df is None:
return pd.DataFrame()
risk_df = risk_df.unstack()
risk_df.columns = risk_df.columns.droplevel(0)
return risk_df.drop("mean", axis=1)
def _get_monthly_risk_analysis_with_report(report_normal_df: pd.DataFrame) -> pd.DataFrame:
"""Get monthly analysis data
:param report_normal_df:
# :param report_long_short_df:
:return:
"""
# Group by month
report_normal_gp = report_normal_df.groupby(
[report_normal_df.index.year, report_normal_df.index.month]
)
# report_long_short_gp = report_long_short_df.groupby(
# [report_long_short_df.index.year, report_long_short_df.index.month]
# )
gp_month = sorted(set(report_normal_gp.size().index))
_monthly_df = pd.DataFrame()
for gp_m in gp_month:
_m_report_normal = report_normal_gp.get_group(gp_m)
# _m_report_long_short = report_long_short_gp.get_group(gp_m)
if len(_m_report_normal) < 3:
# The month's data is less than 3, not displayed
# FIXME: If the trading day of a month is less than 3 days, a breakpoint will appear in the graph
continue
month_days = pd.Timestamp(year=gp_m[0], month=gp_m[1], day=1).days_in_month
_temp_df = _get_risk_analysis_data_with_report(
_m_report_normal,
# _m_report_long_short,
pd.Timestamp(year=gp_m[0], month=gp_m[1], day=month_days),
)
_monthly_df = _monthly_df.append(_temp_df, sort=False)
return _monthly_df
def _get_monthly_analysis_with_feature(
monthly_df: pd.DataFrame, feature: str = "annual"
) -> pd.DataFrame:
"""
:param monthly_df:
:param feature:
:return:
"""
_monthly_df_gp = monthly_df.reset_index().groupby(["level_1"])
_name_df = _monthly_df_gp.get_group(feature).set_index(["level_0", "level_1"])
_temp_df = _name_df.pivot_table(
index="date", values=["risk"], columns=_name_df.index
)
_temp_df.columns = map(lambda x: "_".join(x[-1]), _temp_df.columns)
_temp_df.index = _temp_df.index.strftime("%Y-%m")
return _temp_df
def _get_risk_analysis_figure(analysis_df: pd.DataFrame) -> Iterable[py.Figure]:
"""Get analysis graph figure
:param analysis_df:
:return:
"""
if analysis_df is None:
return []
_figure = SubplotsGraph(
_get_all_risk_analysis(analysis_df), kind_map=dict(kind="BarGraph", kwargs={})
).figure
return (_figure,)
def _get_monthly_risk_analysis_figure(report_normal_df: pd.DataFrame) -> Iterable[py.Figure]:
"""Get analysis monthly graph figure
:param report_normal_df:
:param report_long_short_df:
:return:
"""
# if report_normal_df is None and report_long_short_df is None:
# return []
if report_normal_df is None:
return []
# if report_normal_df is None:
# report_normal_df = pd.DataFrame(index=report_long_short_df.index)
# if report_long_short_df is None:
# report_long_short_df = pd.DataFrame(index=report_normal_df.index)
_monthly_df = _get_monthly_risk_analysis_with_report(
report_normal_df=report_normal_df,
# report_long_short_df=report_long_short_df,
)
for _feature in ["annual", "mdd", "sharpe", "std"]:
_temp_df = _get_monthly_analysis_with_feature(_monthly_df, _feature)
yield ScatterGraph(
_temp_df,
layout=dict(title=_feature, xaxis=dict(type="category", tickangle=45)),
graph_kwargs={"mode": "lines+markers"},
).figure
def risk_analysis_graph(
analysis_df: pd.DataFrame = None,
report_normal_df: pd.DataFrame = None,
report_long_short_df: pd.DataFrame = None,
show_notebook: bool = True,
) -> Iterable[py.Figure]:
"""Generate analysis graph and monthly analysis
Example:
.. code-block:: python
from qlib.contrib.evaluate import risk_analysis, backtest, long_short_backtest
from qlib.contrib.strategy import TopkDropoutStrategy
from qlib.contrib.report import analysis_position
# backtest parameters
bparas = {}
bparas['limit_threshold'] = 0.095
bparas['account'] = 1000000000
sparas = {}
sparas['topk'] = 50
sparas['n_drop'] = 230
strategy = TopkDropoutStrategy(**sparas)
report_normal_df, positions = backtest(pred_df, strategy, **bparas)
# long_short_map = long_short_backtest(pred_df)
# report_long_short_df = pd.DataFrame(long_short_map)
analysis = dict()
# analysis['pred_long'] = risk_analysis(report_long_short_df['long'])
# analysis['pred_short'] = risk_analysis(report_long_short_df['short'])
# analysis['pred_long_short'] = risk_analysis(report_long_short_df['long_short'])
analysis['sub_bench'] = risk_analysis(report_normal_df['return'] - report_normal_df['bench'])
analysis['sub_cost'] = risk_analysis(report_normal_df['return'] - report_normal_df['bench'] - report_normal_df['cost'])
analysis_df = pd.concat(analysis)
analysis_position.risk_analysis_graph(analysis_df, report_normal_df)
:param analysis_df: analysis data, index is **pd.MultiIndex**; columns names is **[risk]**.
.. code-block:: python
risk
sub_bench mean 0.000662
std 0.004487
annual 0.166720
sharpe 2.340526
mdd -0.080516
sub_cost mean 0.000577
std 0.004482
annual 0.145392
sharpe 2.043494
mdd -0.083584
:param report_normal_df: **df.index.name** must be **date**, df.columns must contain **return**, **turnover**, **cost**, **bench**
.. code-block:: python
return cost bench turnover
date
2017-01-04 0.003421 0.000864 0.011693 0.576325
2017-01-05 0.000508 0.000447 0.000721 0.227882
2017-01-06 -0.003321 0.000212 -0.004322 0.102765
2017-01-09 0.006753 0.000212 0.006874 0.105864
2017-01-10 -0.000416 0.000440 -0.003350 0.208396
:param report_long_short_df: **df.index.name** must be **date**, df.columns contain **long**, **short**, **long_short**
.. code-block:: python
long short long_short
date
2017-01-04 -0.001360 0.001394 0.000034
2017-01-05 0.002456 0.000058 0.002514
2017-01-06 0.000120 0.002739 0.002859
2017-01-09 0.001436 0.001838 0.003273
2017-01-10 0.000824 -0.001944 -0.001120
:param show_notebook: Whether to display graphics in a notebook, default **True**
If True, show graph in notebook
If False, return graph figure
:return:
"""
_figure_list = list(_get_risk_analysis_figure(analysis_df)) + list(
_get_monthly_risk_analysis_figure(
report_normal_df,
# report_long_short_df,
)
)
if show_notebook:
ScatterGraph.show_graph_in_notebook(_figure_list)
else:
return _figure_list

View File

@@ -0,0 +1,72 @@
# Copyright (c) Microsoft Corporation.
# Licensed under the MIT License.
import pandas as pd
from ..graph import ScatterGraph
def _get_score_ic(pred_label: pd.DataFrame):
"""
:param pred_label:
:return:
"""
concat_data = pred_label.copy()
concat_data.dropna(axis=0, how="any", inplace=True)
_ic = concat_data.groupby(level="datetime").apply(
lambda x: x["label"].corr(x["score"])
)
_rank_ic = concat_data.groupby(level="datetime").apply(
lambda x: x["label"].corr(x["score"], method="spearman")
)
return pd.DataFrame({"ic": _ic, "rank_ic": _rank_ic})
def score_ic_graph(
pred_label: pd.DataFrame, show_notebook: bool = True
) -> [list, tuple]:
"""score IC
Example:
.. code-block:: python
from qlib.data import D
from qlib.contrib.report import analysis_position
pred_df_dates = pred_df.index.get_level_values(level='datetime')
features_df = D.features(D.instruments('csi500'), ['Ref($close, -2)/Ref($close, -1)-1'], pred_df_dates.min(), pred_df_dates.max())
features_df.columns = ['label']
pred_label = pd.concat([features_df, pred], axis=1, sort=True).reindex(features_df.index)
analysis_position.score_ic_graph(pred_label)
:param pred_label: index is **pd.MultiIndex**, index name is **[instrument, datetime]**; columns names is **[score, label]**
.. code-block:: python
instrument datetime score label
SH600004 2017-12-11 -0.013502 -0.013502
2017-12-12 -0.072367 -0.072367
2017-12-13 -0.068605 -0.068605
2017-12-14 0.012440 0.012440
2017-12-15 -0.102778 -0.102778
:param show_notebook: whether to display graphics in notebook, the default is **True**
:return: if show_notebook is True, display in notebook; else return **plotly.graph_objs.Figure** list
"""
_ic_df = _get_score_ic(pred_label)
# FIXME: support HIGH-FREQ
_ic_df.index = _ic_df.index.strftime("%Y-%m-%d")
_figure = ScatterGraph(
_ic_df,
layout=dict(title="Score IC", xaxis=dict(type="category", tickangle=45)),
graph_kwargs={"mode": "lines+markers"},
).figure
if show_notebook:
ScatterGraph.show_graph_in_notebook([_figure])
else:
return (_figure,)

View File

@@ -0,0 +1,370 @@
# Copyright (c) Microsoft Corporation.
# Licensed under the MIT License.
import math
import importlib
from pathlib import Path
from typing import Iterable
import pandas as pd
import plotly.offline as py
import plotly.graph_objs as go
from plotly.tools import make_subplots
from plotly.figure_factory import create_distplot
from ...utils import get_module_by_module_path
class BaseGraph(object):
""""""
_name = None
def __init__(
self, df: pd.DataFrame = None, layout: dict = None, graph_kwargs: dict = None, name_dict: dict = None, **kwargs
):
"""
:param df:
:param layout:
:param graph_kwargs:
:param name_dict:
:param kwargs:
layout: dict
go.Layout parameters
graph_kwargs: dict
Graph parameters, eg: go.Bar(**graph_kwargs)
"""
self._df = df
self._layout = dict() if layout is None else layout
self._graph_kwargs = dict() if graph_kwargs is None else graph_kwargs
self._name_dict = name_dict
self.data = None
self._init_parameters(**kwargs)
self._init_data()
def _init_data(self):
"""
:return:
"""
if self._df.empty:
raise ValueError("df is empty.")
self.data = self._get_data()
def _init_parameters(self, **kwargs):
"""
:param kwargs
"""
# Instantiate graphics parameters
self._graph_type = self._name.lower().capitalize()
# Displayed column name
if self._name_dict is None:
self._name_dict = {_item: _item for _item in self._df.columns}
@staticmethod
def get_instance_with_graph_parameters(graph_type: str = None, **kwargs):
"""
:param graph_type:
:param kwargs:
:return:
"""
try:
_graph_module = importlib.import_module("plotly.graph_objs")
_graph_class = getattr(_graph_module, graph_type)
except AttributeError:
_graph_module = importlib.import_module("qlib.contrib.report.graph")
_graph_class = getattr(_graph_module, graph_type)
return _graph_class(**kwargs)
@staticmethod
def show_graph_in_notebook(figure_list: Iterable[go.Figure] = None):
"""
:param figure_list:
:return:
"""
py.init_notebook_mode()
for _fig in figure_list:
py.iplot(_fig)
def _get_layout(self) -> go.Layout:
"""
:return:
"""
return go.Layout(**self._layout)
def _get_data(self) -> list:
"""
:return:
"""
_data = [
self.get_instance_with_graph_parameters(
graph_type=self._graph_type, x=self._df.index, y=self._df[_col], name=_name, **self._graph_kwargs
)
for _col, _name in self._name_dict.items()
]
return _data
@property
def figure(self) -> go.Figure:
"""
:return:
"""
return go.Figure(data=self.data, layout=self._get_layout())
class ScatterGraph(BaseGraph):
_name = "scatter"
class BarGraph(BaseGraph):
_name = "bar"
class DistplotGraph(BaseGraph):
_name = "distplot"
def _get_data(self):
"""
:return:
"""
_t_df = self._df.dropna()
_data_list = [_t_df[_col] for _col in self._name_dict]
_label_list = [_name for _name in self._name_dict.values()]
_fig = create_distplot(_data_list, _label_list, show_rug=False, **self._graph_kwargs)
return _fig["data"]
class HeatmapGraph(BaseGraph):
_name = "heatmap"
def _get_data(self):
"""
:return:
"""
_data = [
self.get_instance_with_graph_parameters(
graph_type=self._graph_type,
x=self._df.columns,
y=self._df.index,
z=self._df.values.tolist(),
**self._graph_kwargs
)
]
return _data
class HistogramGraph(BaseGraph):
_name = "histogram"
def _get_data(self):
"""
:return:
"""
_data = [
self.get_instance_with_graph_parameters(
graph_type=self._graph_type, x=self._df[_col], name=_name, **self._graph_kwargs
)
for _col, _name in self._name_dict.items()
]
return _data
class SubplotsGraph(object):
"""Create subplots same as df.plot(subplots=True)
Simple package for `plotly.tools.subplots`
"""
def __init__(
self,
df: pd.DataFrame = None,
kind_map: dict = None,
layout: dict = None,
sub_graph_layout: dict = None,
sub_graph_data: list = None,
subplots_kwargs: dict = None,
**kwargs
):
"""
:param df: pd.DataFrame
:param kind_map: dict, subplots graph kind and kwargs
eg: dict(kind='ScatterGraph', kwargs=dict())
:param layout: `go.Layout` parameters
:param sub_graph_layout: Layout of each graphic, similar to 'layout'
:param sub_graph_data: Instantiation parameters for each sub-graphic
eg: [(column_name, instance_parameters), ]
column_name: str or go.Figure
Instance_parameters:
- row: int, the row where the graph is located
- col: int, the col where the graph is located
- name: str, show name, default column_name in 'df'
- kind: str, graph kind, default `kind` param, eg: bar, scatter, ...
- graph_kwargs: dict, graph kwargs, default {}, used in `go.Bar(**graph_kwargs)`
:param subplots_kwargs: `plotly.tools.make_subplots` original parameters
- shared_xaxes: bool, default False
- shared_yaxes: bool, default False
- vertical_spacing: float, default 0.3 / rows
- subplot_titles: list, default []
If `sub_graph_data` is None, will generate 'subplot_titles' according to `df.columns`,
this field will be discarded
- specs: list, see `make_subplots` docs
- rows: int, Number of rows in the subplot grid, default 1
If `sub_graph_data` is None, will generate 'rows' according to `df`, this field will be discarded
- cols: int, Number of cols in the subplot grid, default 1
If `sub_graph_data` is None, will generate 'cols' according to `df`, this field will be discarded
:param kwargs:
"""
self._df = df
self._layout = layout
self._sub_graph_layout = sub_graph_layout
self._kind_map = kind_map
if self._kind_map is None:
self._kind_map = dict(kind="ScatterGraph", kwargs=dict())
self._subplots_kwargs = subplots_kwargs
if self._subplots_kwargs is None:
self._init_subplots_kwargs()
self.__cols = self._subplots_kwargs.get("cols", 2)
self.__rows = self._subplots_kwargs.get("rows", math.ceil(len(self._df.columns) / self.__cols))
self._sub_graph_data = sub_graph_data
if self._sub_graph_data is None:
self._init_sub_graph_data()
self._init_figure()
def _init_sub_graph_data(self):
"""
:return:
"""
self._sub_graph_data = list()
self._subplot_titles = list()
for i, column_name in enumerate(self._df.columns):
row = math.ceil((i + 1) / self.__cols)
_temp = (i + 1) % self.__cols
col = _temp if _temp else self.__cols
res_name = column_name.replace("_", " ")
_temp_row_data = (
column_name,
dict(
row=row,
col=col,
name=res_name,
kind=self._kind_map["kind"],
graph_kwargs=self._kind_map["kwargs"],
),
)
self._sub_graph_data.append(_temp_row_data)
self._subplot_titles.append(res_name)
def _init_subplots_kwargs(self):
"""
:return:
"""
# Default cols, rows
_cols = 2
_rows = math.ceil(len(self._df.columns) / 2)
self._subplots_kwargs = dict()
self._subplots_kwargs["rows"] = _rows
self._subplots_kwargs["cols"] = _cols
self._subplots_kwargs["shared_xaxes"] = False
self._subplots_kwargs["shared_yaxes"] = False
self._subplots_kwargs["vertical_spacing"] = 0.3 / _rows
self._subplots_kwargs["print_grid"] = False
self._subplots_kwargs["subplot_titles"] = self._df.columns.tolist()
def _init_figure(self):
"""
:return:
"""
self._figure = make_subplots(**self._subplots_kwargs)
for column_name, column_map in self._sub_graph_data:
if isinstance(column_name, go.Figure):
_graph_obj = column_name
elif isinstance(column_name, str):
temp_name = column_map.get("name", column_name.replace("_", " "))
kind = column_map.get("kind", self._kind_map.get("kind", "ScatterGraph"))
_graph_kwargs = column_map.get("graph_kwargs", self._kind_map.get("kwargs", {}))
_graph_obj = BaseGraph.get_instance_with_graph_parameters(
kind,
**dict(
df=self._df.loc[:, [column_name]],
name_dict={column_name: temp_name},
graph_kwargs=_graph_kwargs,
)
)
else:
raise TypeError()
row = column_map["row"]
col = column_map["col"]
_graph_data = getattr(_graph_obj, "data")
# for _item in _graph_data:
# _item.pop('xaxis', None)
# _item.pop('yaxis', None)
for _g_obj in _graph_data:
self._figure.append_trace(_g_obj, row=row, col=col)
if self._sub_graph_layout is not None:
for k, v in self._sub_graph_layout.items():
self._figure["layout"][k].update(v)
self._figure["layout"].update(self._layout)
@property
def figure(self):
return self._figure