본문 바로가기

데이터와 함께 탱고를/데이터 시각화

pandas, bar 그래프(plot) 이쁘게 그리기

이 글의 결과물은 쥬피터에서 아래와 같은 그래프를 그리는 것이다.

이쁘게 그린다고 적어놓긴 했지만, 실제로 다루는 내용은 다음과 같다.

  • 수직, 수평 막대 그래프 위에 값 표시(annotation)하기
  • 수직, 수평 막대 그래프 안에 값 표시하기
  • 그래프 figure 박스 제거

pandas 데이터프레임으로 그래프를 그리는 방법은, matplotlib.pyplpot, pandas.plot(), seaborn 등이 있지만, 여기서는 pandas.plot() 을 기본으로 사용한다.

수직, 수평 막대 그래프 위에 값 표시(annotation)하기

아래 그래프부터 그려보자.

먼저 데이터 프레임부터 보면,

print(df)

년      2017     2018
성별                  
F   1789522  2655864
M   2852440  4467147

이를 기본적으로 df.plot() 으로 그리면, 아래와 같이 그려진다.

df.plot(kind='bar')
plt.show()

색도 줘보고, 제목도 달아주자.

ax = df.plot(kind='bar',  title="년도별 연령대 이용량 변화", rot=0, colors=['slateblue', 'darkslateblue'])
plt.show()

이제 막대 그래프 위에 값을 주자. 아래 코드를 추가한다.

for p in ax.patches:
    left, bottom, width, height = p.get_bbox().bounds
    ax.annotate("%.1f m"%(height/1e6), (left+width/2, height*1.01), ha='center')

하나씩 살펴보면,

  • ax.patches 는 ax가 가르키는 그래프에서, 막대들을 담고있는 리스트다.
    현재 그래프에는 막대가 4개이므로, 4번의 반복문을 돌 것이고, 그때마다, p는 각 막대에 대한 정보를 가리킨다.
  • p.get_bbox().bounds 는 말 그대로 해당 막대그래프의 정보를 나타낸다.
    왼쪽, 아래, 막대그래프의 폭, 높이에 대한 정보를 담는다.
    살짝 살펴보면 아래와 같다. 각 튜플의 왼쪽부터, 왼쪽, 아래, 막대그래프의 폭, 높이임을 알 수 있다.
for p in ax.patches:
    print(p.get_bbox().bounds)

(-0.25, 0.0, 0.25, 1789522.0)
(0.75, 0.0, 0.25, 2852440.0)
(0.0, 0.0, 0.25, 2655864.0)
(1.0, 0.0, 0.25, 4467147.0)
  • ax.annotate(s, xy, *args, **kwargs) 는 그래프 안에 특정 위치에 문자열을 찍어준다.
    • s 인자에 문자열을 넣어주면된다.
    • xy 에는 튜플로 문자열이 들어갈 (x, y) 위치를 넣어주면 된다.
    • 위 코드에서는 (left+width/2, height*1.01) 를 주었는데, 잘 해석해보면,
      • x는 현재 막대그래프의 가운데 위치
      • y는 현재 막대그래프의 높이 위치임을 알 수 있다.
      • y*1.01 은 막대그래프의 높이보다 조금 더 높게(0.01 더 높게) 일종의 마진을 준 것이다. 바꿔도 상관없다.
  • ha 는 짐작할 수 있듯이, horizontal align 이다. 이를 center 로 둬야, 중심축이 가운데로 온다.

결과적으로 다음과 같이 나온다.

수평 그래프도 위에 ax.annotate() 를 잘 생각해보면 된다.
아래와 같은 데이터프레임이 있다고 하자.

print(df)

성별
F    48.411922
M    56.607922

이를 위의 코드를 응용하여 그리면,

ax = df.plot(kind='barh', title="17년 대비 18년도 이용 증가율(%)", rot=0, colors=['red', 'blue'])
for p in ax.patches:
    x, y, width, height = p.get_bbox().bounds
    ax.text(width*1.01, y+height/2, "%.1f %%"%(width), va='center')

값 표시가 그래프 박스에 걸려, 보기가 싫다.
그래프 박스를 지워버리면 훨씬 깔끔하고 보기좋은데, plt.box(False)를 추가하자.

plt.sca(ax)
plt.box(False)

훨씬 보기 좋아졌다.


수직, 수평 막대 그래프 안에 값 표시(annotation)하기

이번엔 아래와 같이 막대 그래프 안에다가 값을 넣어볼 것이다.

다음과 같은 데이터프레임이 있다고 하자.

print(df)

연령대코드      ~10대       20대       30대       40대       50대       60대      70대~
년                                                                          
2017   0.026147  0.483220  0.261169  0.148048  0.058440  0.015461  0.007515
2018   0.018715  0.480448  0.262697  0.149113  0.067326  0.015917  0.005784

기본적인 틀은 수직 막대그래프와 다르지 않다.
ax.annotate()xy 값만 조금 달라지는데, 어떻게 달라지는지 잘 보자.

ax = df.plot(kind='barh', stacked=True, title="년도별 연령대 이용비율(%)", rot=0)
for p in ax.patches:
    left, bottom, width, height = p.get_bbox().bounds
    ax.annotate("%.1f"%(width*100), xy=(left+width/2, bottom+height/2), ha='center', va='center')
plt.box(False)
plt.show()

xy=(left+width/2, bottom+height/2), ha='center', va='center' 가 핵심이다.

  • left+width/2 는 현재 막대 그래프의 가운데 x위치, bottom+height/2 는 y위치값을 갖는다.
  • ha=center, va=center 로, 현재 막대 그래프의 축을 수평, 수직 가운데로 정렬한다.

수직도 이와 비슷하다.

for p in ax.patches:
    left, bottom, width, height = p.get_bbox().bounds
    ax.annotate("%.1f"%(height*100), xy=(left+width/2, bottom+height/2), ha='center', va='center')
plt.sca(ax)
plt.box(False)

xy=(left+width/2, bottom+height/2), ha='center', va='center' 의 의미를 잘 해석해보면 된다.

 

반응형
  • 2021.09.04 16:23

    비밀댓글입니다

    • BlogIcon 흠시 2021.09.04 16:32 신고

      안녕하세요. 요즘 시각화 업무에 손 놓은지가 오래라... 저도 잘 모를 수 있습니다.

      일단 위에 제가 쓴 글을 보니 pandas.Dataframe.plot() 함수를 쓰고 있는데요. 문의주신 코드를 보았을 때는 matplotlib 를 쓰고있는거 같군요.

      pandas.Dataframe.plot() 의 경우 반환되는 객체가 matplotlib.axes.Axes 이고 .annotation() 함수도 이 객체에서 제공합니다.

      한편 코드에서 사용하신 matplotlib.pyplot.barh() 함수는 matplotlib.container.BarContainer 객체를 반환합니다. 이 객체는 .annotation() 함수를 제공하지 않는군요.

      따라서 위 같은 에러가 나는 것이구요...
      해결 방법은 matplotlib이 아닌 위 코드처럼 pandas.Dataframe.plot 을 사용하시거나, matplotlib 에서 matplotlib.axes.Axes 를 어떻게 얻을 수 있는지 찾아보셔야할거 같아요~

      참고:
      https://pandas.pydata.org/docs/reference/api/pandas.DataFrame.plot.html

      https://matplotlib.org/3.1.1/api/_as_gen/matplotlib.pyplot.barh.html