가볍게 배우고 깊게 즐기고 오래 남기기

[Error] riskfolio모듈 plot pie 에러 - " pie() got an unexpected keyword argument 'no 본문

Programming & Tip/Python

[Error] riskfolio모듈 plot pie 에러 - " pie() got an unexpected keyword argument 'no

Awesomist 2023. 1. 22. 01:48
728x90


포트폴리오 최적화와 자산 배분 전략에 용이한 riskfolio 모듈에는
꽤 잘 만들어놓은 도넛차트가 있는데, 이게 Colab이나 최신화된 환경에서
우리가 보고 싶은 이 화면 공식문서의 코드를 그대로 써도 나오지 않는다.

https://riskfolio-lib.readthedocs.io/en/latest/



설치할 때 잘 째려보면 matplotlib.pyplot을 사용하고 있음을 알 수 있는데
이 모듈이 참조하는 과정에서 normalize 파라미터가 문제가 있어보였다.
riskfolio모듈의 plot_pie에는 입력변수가 자체가 정의되어있지 않기 때문에 백날천날 이 함수의 파라미터로 두드려봐야 큰 의미가 없다.


아마 개발시점의 pyplot의 개발 구조와 현 시점의 개발 구조가 달라져서인 듯 한데,
애석하게되 단순히 기존 matplotlib을 삭제하고 과거 버전으로 회귀한다고 해서 해결되지 않는다. (이미 다 해봄)




그래서 직접 원본 소스를 까서 normalize를 찾아보기로 했다.

잡았다 요놈

normalize를 None으로 해도 같은 에러가 반복되는 것을 확인해서 이 코드 자체를 무효화 시키기고
아예 내 코드 내에서 함수를 재정의해서 접근하기로 한다.


기존의 Plot_pie가 import하는 영역들을 체크하고 필수적으로 넣어줘야 하므로
원본 소스를 쓱 훑어보니 다행히 다른 커스텀 모듈없이
matplotlib.pyplot 과 cm, colors 정도만 사용하는 것 같아서 해당 두 개 모듈만 취하고 나머지는 고민없이 삭제한다.



1) 모듈의 plot_pie을 변경해 사용자정의함수 정의하기

( ↓ ) plot_pie 재정의 코드 펼치기

더보기

(Code)

# Plot_pie 코드를 일부 변경해 적용한 코드 
# 기존의 Plot_pie가 import하는 영역들을 체크하고 필수적으로 넣어줘야 함


import matplotlib.pyplot as plt 
from matplotlib import cm, colors

def plot_pie(
    w, title="", others=0.05, nrow=25, cmap="tab20", height=6, width=8, ax=None
):
  
    if not isinstance(w, pd.DataFrame):
        raise ValueError("w must be a DataFrame")

    if w.shape[1] > 1 and w.shape[0] == 0:
        w = w.T
    elif w.shape[1] > 1 and w.shape[0] > 0:
        raise ValueError("w must be a column DataFrame")

    if ax is None:
        ax = plt.gca()
        fig = plt.gcf()
        fig.set_figwidth(width)
        fig.set_figheight(height)
    else:
        fig = ax.get_figure()

    labels = w.index.tolist()
    sizes = w.iloc[:, 0].tolist()
    abs_sizes = [np.absolute(s) for s in sizes]
    sizes2 = pd.DataFrame([labels, abs_sizes, sizes]).T
    sizes2.columns = ["labels", "abs_values", "values"]
    sizes2 = sizes2.sort_values(by=["abs_values"], ascending=False)
    sizes2.index = [i for i in range(0, len(labels))]
    sizes3 = sizes2.cumsum()
    sizes3["abs_values"] = sizes3["abs_values"] / sizes3["abs_values"].max()
    l = sizes3[sizes3["abs_values"] >= 1 - others].index.tolist()[0]

    if l > 0:
        a1 = sizes2["abs_values"].sum() - sizes2[sizes2.index <= l]["abs_values"].sum()
        a2 = sizes2["values"].sum() - sizes2[sizes2.index <= l]["values"].sum()
        item = pd.DataFrame(["Others", a1, a2]).T
        item.columns = ["labels", "abs_values", "values"]
        sizes2 = sizes2[sizes2.index <= l]
        sizes2 = sizes2.append(item)

    abs_sizes = sizes2["abs_values"].tolist()
    sizes = sizes2["values"].tolist()
    labels = sizes2["labels"].tolist()
    sizes2 = ["{0:.1%}".format(i) for i in sizes]

    if title == "":
        title = "Portfolio Composition"

    limit = np.round(np.min(sizes), 4)
    if limit < 0:
        title += " (Areas in Absolute Values)"

    ax.set_title(title)

    colormap = cm.get_cmap(cmap)
    colormap = colormap(np.linspace(0, 1, 20))

    if cmap == "gist_rainbow":
        colormap = colormap[::-1]

    cycle = plt.cycler("color", colormap)
    ax.set_prop_cycle(cycle)

    size = 0.4

    # set up style cycles

    wedges, texts = ax.pie(
        abs_sizes,
        radius=1,
        wedgeprops=dict(width=size, edgecolor="black"),
        startangle=-15
        #normalize=True        #이 부분을 무효화시킨다. None을 넣어도 동일하게 에러가 난다 
    )

    # Equal aspect ratio ensures that pie is drawn as a circle.

    ax.axis("equal")

    n = int(np.ceil(l / nrow))
    if n == 0:
        n += 1

    ax.legend(wedges, labels, loc="center left", bbox_to_anchor=(1, 0.5), ncol=n)

    bbox_props = dict(boxstyle="square,pad=0.3", fc="w", ec="k", lw=0.72)
    kw = dict(
        xycoords="data",
        textcoords="data",
        arrowprops=dict(arrowstyle="-"),
        bbox=bbox_props,
        zorder=0,
        va="center",
    )

    for i, p in enumerate(wedges):
        ang = (p.theta2 - p.theta1) / 2.0 + p.theta1
        y = np.sin(np.deg2rad(ang))
        x = np.cos(np.deg2rad(ang))
        horizontalalignment = {-1: "right", 1: "left"}[int(np.sign(x))]
        connectionstyle = "angle,angleA=0,angleB={}".format(ang)
        kw["arrowprops"].update({"connectionstyle": connectionstyle})
        name = str(labels[i]) + " " + str(sizes2[i])
        ax.annotate(
            name,
            xy=(x, y),
            xytext=(1.1 * np.sign(x), 1.1 * y),
            horizontalalignment=horizontalalignment,
            **kw
        )

    try:
        fig.tight_layout()
    except:
        pass

    return ax



2) rp모듈의 plot_pie함수가 아닌 새로 정의한 plot_pie로 적용하기

As-is To-be
ax = rp.plot_pie(w=w,
                 title='Title',
                 others=0.05,
                 nrow=25,
                 cmap="tab20",
                 height=8,
                 width=10,
                 ax=None)
ax = plot_pie(w=w,
                 title='Title',
                 others=0.05,
                 nrow=25,
                 cmap="tab20",
                 height=8,
                 width=10,
                 ax=None)




3) 최종결과 - 해결

 

 

 

 

 

 

 

 

※ 쿠팡 파트너스 활동을 통해 일정액의 수수료를 제공받을 수 있습니다.

반응형
Comments