Plots

R for Data Science by Wickham & Grolemund

Author

Sungkyun Cho

Published

March 29, 2025

데이터 분석의 과정


source: R for Data Science

  • Transform (데이터 변형)
    • 데이터의 변수들 중 일부를 선택하기
    • 필요한 부분를 필터링하기
    • 기존의 변수들로 새로운 변수 만들기
    • 요약자료를 계산하기
  • Visualise (시각화)
    • 시각화를 통해 데이터가 품고 있는 정보를 파악하여 데이터에 대한 이해를 높임
  • Model (모형)
    • 시각화와 데이터 변형의 두 가지를 병행하면서 호기심과 의구심을 갖고 연구자가 자신의 관심사에 답을 구하는 탐색적 분석을 하는 과정
    • 이 과정에서 모형을 세우고 데이터를 얼마나 잘 설명하는지를 살펴보고, 모형을 수정해 나가는 과정을 거침

First steps

Load packages
# numerical calculation & data frames
import numpy as np
import pandas as pd

# visualization
import matplotlib.pyplot as plt
import seaborn as sns
import seaborn.objects as so

# statistics
import statsmodels.api as sm

# pandas options
pd.set_option('mode.copy_on_write', True)  # pandas 2.0
pd.options.display.float_format = '{:.2f}'.format  # pd.reset_option('display.float_format')
pd.options.display.max_rows = 7  # max number of rows to display

# NumPy options
np.set_printoptions(precision = 2, suppress=True)  # suppress scientific notation

# For high resolution display
import matplotlib_inline
matplotlib_inline.backend_inline.set_matplotlib_formats("retina")

Data: Fuel economy data from 1999 to 2008 for 38 popular models of cars

# import the dataset
mpg_data = sm.datasets.get_rdataset("mpg", "ggplot2")
mpg = mpg_data.data
# Description
print(mpg_data.__doc__)
mpg
    manufacturer   model  displ  year  cyl       trans drv  cty  hwy fl  \
0           audi      a4   1.80  1999    4    auto(l5)   f   18   29  p   
1           audi      a4   1.80  1999    4  manual(m5)   f   21   29  p   
2           audi      a4   2.00  2008    4  manual(m6)   f   20   31  p   
..           ...     ...    ...   ...  ...         ...  ..  ...  ... ..   
231   volkswagen  passat   2.80  1999    6    auto(l5)   f   16   26  p   
232   volkswagen  passat   2.80  1999    6  manual(m5)   f   18   26  p   
233   volkswagen  passat   3.60  2008    6    auto(s6)   f   17   26  p   

       class  
0    compact  
1    compact  
2    compact  
..       ...  
231  midsize  
232  midsize  
233  midsize  

[234 rows x 11 columns]

Q: 엔진의 크기(displ)와 연비(hwy)는 어떤 관계에 있는가?

# Scatter plot: 산포도
(
    so.Plot(mpg, x="displ", y="hwy") # empty plot을 생성하고, x, y축에 mapping할 mpg 데이터의 변수를 지정
    .add(so.Dot()) # layer를 추가하여, points들을 Dot이라는 mark object를 써서 표현
)

Layer-specific mappings

Global vs. local mapping

다음과 같이 첫번째 layer 안에서 x, y를 mapping하는 경우, 이후 새로 추가되는 layer에는 그 mapping이 적용되지 않음

(
    so.Plot(mpg)
    .add(so.Dot(), x="displ", y="hwy") # 이 layer에서만 mapping이 유효
)
Tip

다음과 같이 x, y를 생략하거나 간략히 할 수 있으나…

so.Plot(mpg, "displ", "hwy").add(so.Dot())

카테고리 변수인 경우

  • cyl (실린더 개수), hwy (고속도로 연비)의 관계를 scatterplot으로 살펴볼 수 있는가? (left)
  • class (차량 타입), drv (전륜 구동, 후륜 구동, 4륜 구동 타입)의 관계는 어떠한가? (right)

Aesthetic mappings

Q: 엔진의 크기와 연비와의 관계에서 보이는 트렌드 라인에서 심하게 벗어난 것이 있는가?


변수들을 x, y라는 position에 mapping하는 것에 추가하여 다음과 같은 속성(aesthetic)에 mapping할 수 있음

색(color), 크기(pointsize), 모양(marker), 선 종류(linestyle), 투명도(alpha)

Color

(
    so.Plot(mpg, x="displ", y="hwy", color="class")
    .add(so.Dot())
)

Pointsize

(
    so.Plot(mpg, x="displ", y="hwy", pointsize="class")
    .add(so.Dot())
)

Marker

(
    so.Plot(mpg, x="displ", y="hwy", marker="class")
    .add(so.Dot())
)

Alpha

(
    so.Plot(mpg, x="displ", y="hwy", alpha="class")
    .add(so.Dot())
)

Linestyle

healthexp = sns.load_dataset("healthexp")

p = so.Plot(healthexp, x="Spending_USD", y="Life_Expectancy", linestyle="Country")
p.add(so.Line())

두 가지 이상의 속성

ex. color & marker

(
    so.Plot(mpg, x="displ", y="hwy", color="class", marker="drv")
    .add(so.Dot())
)

(
    so.Plot(mpg, x="displ", y="hwy", color="class", pointsize="drv")
    .add(so.Dot())
    .scale(pointsize=(5, 15))  # pointsize의 range설정
)

Note

아래 그림에서처럼 연속 vs. 카테고리 변수 여부에 따라 다르게 작동

Important

어떤 속성을 어떤 변수에 할당하는 것이 적절한지를 선택하는 것이 기술
예를 들어, 아래 두 플랏은 동일한 정보를 품고 있으나, 시각적 인식에 큰 차이를 만듦

Setting properties

Setting properties vs. mapping properties (aesthetic)

변수에 속성을 할당하는 것이 아니라, graphical objects (Marks)의 속성을 지정

Marks (Dot(), Line(), Bar(), …) 마다 설정할 수 있는 속성이 다름
주로 쓰이는 속성들: color, pointsize, alpha

Tip

다양한 Mark properties에 대해서는 홈페이지 참고
Properties of Mark objects

Note

.Dot()의 경우
class seaborn.objects.Dot(artist_kws=, marker=<‘o’>, pointsize=<6>, stroke=<0.75>, color=<‘C0’>, alpha=<1>, fill=, edgecolor=, edgealpha=, edgewidth=<0.5>, edgestyle=<‘-’>)

.Dots()의 경우
class seaborn.objects.Dots(artist_kws=, marker=<rc:scatter.marker>, pointsize=<4>, stroke=<0.75>, color=<‘C0’>, alpha=<1>, fill=, fillcolor=, fillalpha=<0.2>)

API reference 참고

(
    so.Plot(mpg, x="displ", y="hwy")
    .add(so.Dot(color="deepskyblue")) # Mark object 안에 지정!
)

(
    so.Plot(mpg, x="displ", y="hwy")
    .add(so.Dot(color="deepskyblue", pointsize=12, edgecolor="white", edgewidth=1)) # Mark object 안에 지정!
)

(
    so.Plot(mpg, x="displ", y="hwy")
    .add(so.Dot(color="orange", pointsize=12, marker=">", alpha=.4))  # Mark object 안에 지정!
)

Faceting

카테고리 변수들이 지니는 카테고리들(레벨)별로 나누어 그리기

The penguins data: penguins in the Palmer Archipelago, Antarctica.


Artwork by @allison_horst

penguins = sns.load_dataset("penguins") # load a dataset: penguins
penguins.head()
  species     island  bill_length_mm  bill_depth_mm  flipper_length_mm  \
0  Adelie  Torgersen           39.10          18.70             181.00   
1  Adelie  Torgersen           39.50          17.40             186.00   
2  Adelie  Torgersen           40.30          18.00             195.00   
3  Adelie  Torgersen             NaN            NaN                NaN   
4  Adelie  Torgersen           36.70          19.30             193.00   

   body_mass_g     sex  
0      3750.00    Male  
1      3800.00  Female  
2      3250.00  Female  
3          NaN     NaN  
4      3450.00  Female  
(
    so.Plot(penguins, x="body_mass_g", y="flipper_length_mm")
    .add(so.Dot(alpha=.5))
    .facet("sex")  # 기본적으로 columns으로 나누어져 그림, wrap: column에 몇 개까지 그릴지
)

p = (
    so.Plot(penguins, x="body_mass_g", y="flipper_length_mm")
    .facet(col="species", row="sex")
    .add(so.Dot(alpha=.5))
)
p

# x, y축의 눈금을 일치할지 여부
p.share(x=False, y=True)

Important

Facet과 Color 중 어떤 방식으로 표현하는 것이 유리한가? 밸런스를 잘 선택!

left = (
    so.Plot(penguins, x="body_mass_g", y="flipper_length_mm")
    .facet(col="species")
    .add(so.Dot(alpha=.5))
)
right = (
    so.Plot(penguins, x="body_mass_g", y="flipper_length_mm", color="species")
    .add(so.Dot(alpha=.5))
)

bottom = (
    so.Plot(penguins, x="body_mass_g", y="flipper_length_mm")
    .facet(row="species")
    .add(so.Dot(alpha=.5))
)

display(left, right, bottom)

facetting; horizontal

colors

facetting; vertical
Figure 1: Facetting by row, column, or color

Pairing

Faceting이 변수 내에 다른 레벨에 따라 그려지는데 반해,
paring은 x, y축에 다른 변수를 지정하여 그림

(
    so.Plot(penguins, y="body_mass_g", color="species")  # y축은 공유
    .pair(x=["bill_length_mm", "bill_depth_mm"])  # x축에 다른 변수를 mapping
    .add(so.Dots())  # .Dots()! overploting에 유리. .Dot(alpha=.)로도 비슷
)

Facet & pair 동시

(
    so.Plot(penguins, y="body_mass_g", color="sex")
    .pair(x=["bill_length_mm", "bill_depth_mm"])
    .facet(row="species")
    .add(so.Dots())
)

Multiple plots

개발 중? Matplotlib을 통해 구현

import matplotlib as mpl

f = mpl.figure.Figure(figsize=(8, 4))
sf1, sf2 = f.subfigures(1, 2)
(
    so.Plot(penguins, x="body_mass_g", y="flipper_length_mm")
    .add(so.Dots())
    .on(sf1)
    .plot()
)
(
    so.Plot(penguins, x="bill_length_mm", y="flipper_length_mm")
    .facet(row="sex")
    .add(so.Dots())
    .on(sf2)
    .plot()
)

여러 변수들에 대해 loop를 통해 플롯을 그리려면,

f = mpl.figure.Figure(figsize=(6, 6), layout="constrained")
subfigs = f.subfigures(2, 2)

Xs = ["bill_length_mm", "bill_depth_mm", "flipper_length_mm"]
for sf, xvar in zip(subfigs.flatten(), Xs):
    p = (
            so.Plot(penguins, x=xvar, y="body_mass_g", color="species")
            .add(so.Dots())
            .on(sf)
            .plot()
        )
p

플롯 파일로 저장하기

p.save("data/filename.png") # p: a plot oject

Geometric objects

  • Dot marks: Dot, Dots
  • Line marks: Line, Lines, Path, Paths, Dash, Range
  • Bar marks: Bar, Bars
  • Fill marks: Area, Band
  • Text marks: Text

API reference: Mark objects

Statistical transformations

Agg, Est, Count, Hist, KDE, Perc, PolyFit

Important

위의 통계 함수들을 이용하여 변형된 데이터 값을 geometric objects에 mapping하여 다양한 플랏을 그릴 수 있음
원칙적으로는 직접 통계치을 계산한 후에 그 데이터로 플랏을 그릴 수 있으나, 신속한 탐색적 분석을 위해 사용

Note

현재 seaborn.objects에서 다음 두 가지 중요한 statistical transformations이 제공되지 않고 있음

  • (non-parametirc) fitted line을 보여주는 loess or GAM line
  • 분포의 간략한 summary인 boxplot

이 부분에 대해서는 아래 몇 가지 대안이 있음; 아래에서 설명

Fitted Lines 구하기: Machine Learning Algorithms

Data에 fitted line를 구하는 방식에는 여러 방법이 있음

  • Linear fit: 1차 함수형태로 fit
  • Smoothing fit
    • Polynominal fit: n차 다항함수형태로 fit
    • GAM: generalized additive model
      • Spline: piece-wise polynominal regression
    • Loess/lowess: locally estimated/weighted scatterplot smoothing

나중에 좀 더 자세히 알아봄
현재 seaborn.objects에서는 polynomial fit만 제공

Fitted lines

(
    so.Plot(mpg, x="displ", y="hwy")
    .add(so.Dot())
    .add(so.Line(), so.PolyFit(5))  # PolyFit(n): n차 다항식으로 fit
)

(
    so.Plot(mpg, x="displ", y="hwy")
    .add(so.Line(), so.PolyFit(5))  # PolyFit(n): n차 다항식으로 fit
)

(
    so.Plot(mpg, x="displ", y="hwy", color="drv")  # color mapping이 이후 모든 layer에 적용
    .add(so.Dot())
    .add(so.Line(), so.PolyFit(5))
)

(
    so.Plot(mpg, x="displ", y="hwy")
    .add(so.Dot(), color="drv")  # color mapping이 이 layer에만 적용
    .add(so.Line(), so.PolyFit(5))
)

(a) color가 모든 layers에 적용: global mapping
(b) color가 두번째 layer에만 적용: local mapping
Figure 2: Inherited mapping
(
    so.Plot(mpg, x="displ", y="hwy")
    .add(so.Dot(), color="drv")
    .add(so.Line(), so.PolyFit(5), group="drv") # color가 아닌 group으로 grouping
)
# 다항함수 fit의 특징 및 주의점

Linear fit vs. smoothing fit:
선형적인 트렌드에서 얼마나 벗어나는가?

(
    so.Plot(mpg, x="displ", y="hwy")
    .add(so.Dot(color=".6"))
    .add(so.Line(), so.PolyFit(5))
    .add(so.Line(), so.PolyFit(1))
)

Seaborn.objects 요약

(
    so.Plot(df, x=, y=, color=, ...)  # global mapping
    .add(so.Dot(color=, pointsize=,...))  # mark object + setting properties
    .add(so.Line(), x=, y=, color=, ...)  # local mapping
    .add(so.Line(), so.Polyfit(5))  # 통계적으로 변환한 값을 Line plot으로 표현
    .add(so.Bar(), so.Hist(stat="proportion"))  # 통계적으로 변환한 값을 Bar plot로 표현
    ...
    .facet(col=, row=, wrap=) # 카테고리의 levels에 따라 나누어 표현
)
  1. Aesthetic mapping

    • 위치(position): x축, y
    • 색(color), 크기(pointsize), 모양(marker), 선 종류(linestyle), 투명도(alpha)
    • global vs. local mapping
  2. Geometric objects

    • Dot marks: Dot, Dots
    • Line marks: Line, Path, Dash, Range
    • Bar marks: Bar, Bars
    • Fill marks: Area, Band
    • Text marks: Text
  3. Setting properties

    • Marks (.Dot(), .Line(), .Bar(), …) 내부에 속성을 지정하고, marks마다 설정할 수 있는 속성이 다름.
    • 주로 쓰이는 속성들: color, pointsize, alpha
  4. Statistical transformations

    • 변수들을 통계적 변환 후 그 값을 이용
    • Agg, Est, Count, Hist, KDE, Perc, PolyFit
  5. Faceting: 카테고리 변수들의 levels에 따라 나누어 그림

Applications

Visualizing distributions

분포를 살펴보는데 변수가 연속인지 카테고리인지에 따라 다른 방식

A categorical variable

tips = sns.load_dataset("tips")
tips.info()
<class 'pandas.core.frame.DataFrame'>
RangeIndex: 244 entries, 0 to 243
Data columns (total 7 columns):
 #   Column      Non-Null Count  Dtype   
---  ------      --------------  -----   
 0   total_bill  244 non-null    float64 
 1   tip         244 non-null    float64 
 2   sex         244 non-null    category
 3   smoker      244 non-null    category
 4   day         244 non-null    category
 5   time        244 non-null    category
 6   size        244 non-null    int64   
dtypes: category(4), float64(2), int64(1)
memory usage: 7.4 KB
(
    so.Plot(tips, x="day")
    .add(so.Bar(), so.Count())  # category type의 변수는 순서가 존재. 
                                # 그렇지 않은 경우 알바벳 순서로. 
)

Note

복잡한 통계치의 경우 직접 구한후 plot을 그리는 것이 용이

count_day = tips.value_counts("day", normalize=True).reset_index(name="pct")
#     day  pct
# 0   Sat 0.36
# 1   Sun 0.31
# 2  Thur 0.25
# 3   Fri 0.08
(
    so.Plot(count_day, x="day", y="pct")
    .add(so.Bar())
)
penguins = sns.load_dataset("penguins") # load a dataset: penguins

# Species에 inherent order가 없음; 알파벳 순으로 정렬
(
    so.Plot(penguins, x="species")
    .add(so.Bar(), so.Count())
)

(
    so.Plot(penguins, x="species")
    .add(so.Bar(), so.Hist("proportion"))  # Hist()의 default는 stat="count"
)

# grouping의 처리에 대해서는 뒤에... 에를 들어, color="sex"

Important

표시 순서를 변경하는 일은 의미있는 플랏을 만드는데 중요
나중에 좀 더 자세히 다룸

# value_counts()는 크기대로 sorting!
reorder = penguins.value_counts("species").index.values
#> array(['Adelie', 'Gentoo', 'Chinstrap'], dtype=object)

(
    so.Plot(penguins, x="species")
    .add(so.Bar(), so.Count())
    .scale(x=so.Nominal(order=reorder))  # x축의 카테고리 순서를 변경
)

# 직접 개수를 구해 그리는 경우, 테이블의 순서대로 그려짐
(
    so.Plot(penguins.value_counts("species").reset_index(), 
            x="species", y="count")
    .add(so.Bar())
)

A numerical variable

(
    so.Plot(penguins, x="body_mass_g")
    .add(so.Bars(), so.Hist())  # Histogram; x값을 bins으로 나누어 count를 계산!
                                # .Bars()는 .Bar()에 비해 연속변수에 더 적합: 얇은 경계선으로 나란히 붙혀서 그려짐
)

(
    so.Plot(penguins, x="body_mass_g")
    .add(so.Bars(), so.Hist(binwidth=100))  # binwidth vs. bins
)
(
    so.Plot(penguins, x="body_mass_g")
    .add(so.Bars(), so.Hist(bins=10))  # binwidth vs. bins
)
(a) binwidth=100
(b) bins=10
Figure 3: binwidth vs. bins
(
    so.Plot(penguins, x="body_mass_g")
    .add(so.Bars(), so.Hist("proportion"))  # 비율을 계산; stat="count"가 default
)

# Density plot: 넓이가 1이 되도록
(
    so.Plot(penguins, x="body_mass_g")
    .add(so.Area(), so.KDE())  # Density plot
)

# Density plot: 넓이가 1이 되도록
(
    so.Plot(penguins, x="body_mass_g")
    .add(so.Line(color="orange"), so.KDE(bw_adjust=.2))  # Density bandwidth: binwidth에 대응
    .add(so.Bars(alpha=.3), so.Hist("density", binwidth=100))  # stat="density"
)

pandas의 method를 이용한 여러 히스토그램 그리기

pandas의 hist() method: 모든 연속 변수에 대해 histogram을 그림

penguins.hist(bins=20);  # 파라미터 figsize=(6, 5)

# 세미콜론(;) 대신 plt.show() 쓸수도 있음

seaborn 스타일로 histogram 그리기
sns.set_theme()
penguins.hist(bins=20);

sns.set_theme(): set aspects of the visual theme for all matplotlib and seaborn plots.
hist() 메서드가 matplotlib 대신 seaborn 스타일로 그려짐.

Visualizing relationships

A numerical and a categorical variable

  • Boxplot
  • Grouped distribution: histogram, frequency polygon, density plot

Boxplot

source: R for Data Science

각각에 상응하는 분포(KDE, kernel density plot)

  • 한쪽으로 쏠린 정도; 왜도(skewness)
  • 퍼져 있는 정도 정도; 표준편차
    • 정규분포로부터 벗어나는 정도: 첨도(kurtosis)

Seaborn 함수인 boxplot()을 이용하면,

sns.boxplot(penguins, x="species", y="body_mass_g", fill=False);  # fill: box의 색을 채울지 여부

Seaborn.object를 활용하면,

(
    so.Plot(penguins, x="species", y="body_mass_g")
    .add(so.Dot(pointsize=8), so.Agg("median"))  # Agg(): aggregation, default는 mean
    .add(so.Range(), so.Est(errorbar=("pi", 50)))   # Range(): 기본 min/max range, 
                                                    # Est(): estimator
)

(
    so.Plot(penguins, x="species", y="body_mass_g")
    .add(so.Dots(color=".5"), so.Jitter()) # so.Jitter(): 흐트려뜨려 그리기
    .add(so.Dot(pointsize=8), so.Agg("median"))
    .add(so.Range(), so.Est(errorbar=("pi", 50)))
)

Error bars에 대해서는 seaborn/statistical estimation and error bars

(
    so.Plot(penguins, x="species", y="body_mass_g", color="sex")
    .add(so.Dots(), so.Jitter(), so.Dodge())
    .add(so.Dot(pointsize=5), so.Agg("median"), so.Dodge())
    .add(so.Range(), so.Est(errorbar=("pi", 50)), so.Dodge())
)

# seabron boxplot()에서는 hue로 color mapping
sns.boxplot(penguins, x="species", y="body_mass_g", hue="sex", fill=False);

# facetting을 하려면 catplot()을 이용
sns.catplot(
    data=penguins, x="species", y="bill_length_mm", hue="sex", col="island",
    kind="box", fill=False, height=3.5, aspect=.7,
);

# Build a boxplot!
def boxplot(df, x, y, color=None, alpha=0.1):
    return (
        so.Plot(df, x=x, y=y, color=color)
        .add(so.Dots(alpha=alpha, color=".6"), so.Jitter(), so.Dodge())
        .add(so.Range(), so.Est(errorbar=("pi", 50)), so.Dodge())
        .add(so.Dots(pointsize=8, marker="<"), so.Agg("median"), so.Dodge())
        .scale(color="Dark2")
        .theme({**sns.axes_style("whitegrid")})
    )

(
    boxplot(penguins, x="species", y="flipper_length_mm", color="sex")
    .facet("island")
    .layout(size=(8, 4))
)

# Build a rangeplot!
def rangeplot(df, x, y, color=None):
    return (
        so.Plot(df, x=x, y=y, color=color)
        .add(so.Range(), so.Est(errorbar=("pi", 50)), so.Dodge())
        .add(so.Dots(pointsize=8, marker="<"), so.Agg("median"), so.Dodge())
        .scale(color="Dark2")
        .theme({**sns.axes_style("whitegrid")})
    )

(
    rangeplot(penguins, x="species", y="flipper_length_mm", color="sex")
    .facet("island")
    .layout(size=(8, 4))
)

Histogram

# 별로 유용하지 못함
(
    so.Plot(penguins, x="body_mass_g", color="species")
    .add(so.Bars(), so.Hist(common_bins=False))  # bins을 공유하지 않도록
)
# Hist(): 다양한 parameter가 있음.

Frequency polygon

(
    so.Plot(penguins, x="body_mass_g", color="species")
    .add(so.Line(marker="."), so.Hist(binwidth=200))  # Line에 maker "."을 표시
)

# 비율로 표시 & 각 카테고리/레벨 별로 비율 계산
(
    so.Plot(penguins, x="body_mass_g", color="species")
    .add(
        so.Line(marker="."), 
        so.Hist(binwidth=200, stat="proportion", common_norm=False)
    )  
)

Density plot

(
    so.Plot(penguins, x="body_mass_g", color="species")
    .add(so.Area(), so.KDE(common_norm=False))  # Density plot, species별로 넓이가 1이 되도록
)

Two categorical variables

p = so.Plot(penguins, x="island", color="species")
p.add(so.Bar(), so.Count()) # Bar() mark + Count() transformation

p.add(so.Bar(), so.Count(), so.Dodge())   # 나란히 표시
p.add(so.Bar(), so.Count(), so.Stack())  # stacking
(a) dodge
(b) stack
Figure 4: dodge vs. stack

Count 대신 proportion을 표시하는 경우: Hist()를 사용
Count()Hist(stat="count")와 동일함.

# 각 비율값의 합이 1이 되도록, 즉 모든 카테고리에 걸쳐 normalize
p.add(
    so.Bar(width=.5), 
    so.Hist("proportion"),  # proportion; stat="count"로 하면 앞서 so.Count()와 동일
    so.Stack()  # stacking
)

# x축 기준으로 normalize
p.add(
    so.Bar(width=.5), 
    so.Hist("proportion", common_norm=["x"]),  # proportion; 
    so.Stack()  # stacking
)
# warning이 뜰 수 있음!

# x축, facet의 column 기준으로 normalize
p.add(
    so.Bar(width=.8), 
    so.Hist("proportion", common_norm=["x", "col"]),  # proportion
    so.Stack(),  # stacking
).facet(col="sex")  # faceting

common_norm: True vs. False 비교

# 각 비율값의 합이 1이 되도록, 즉 모든 카테고리에 걸쳐 normalize
p.add(
    so.Bar(width=.5), so.Hist("proportion", common_norm=True),  # default
    so.Stack()
)

# 각 species별로 normalize 
p.add(
    so.Bar(width=.5), so.Hist("proportion", common_norm=False),
    so.Stack()
)

Two numerical variables

Scatterplot

(
    so.Plot(penguins, x="flipper_length_mm", y="body_mass_g")
    .add(so.Dot())  # overplotting에는 so.Dots()가 유리 
)

Three or more variables

(
    so.Plot(penguins, x="flipper_length_mm", y="body_mass_g",
            color="species", marker="island")
    .add(so.Dot())
    .layout(size=(6, 4))  # plot size 조정
)

Facet의 활용

(
    so.Plot(penguins, x="flipper_length_mm", y="body_mass_g",
            color="species")
    .add(so.Dot(alpha=.5))
    .facet("island")
    .layout(size=(8, 4))
)

Time series

healthexp = sns.load_dataset("healthexp")

(
    so.Plot(healthexp, x="Year", y="Spending_USD", color="Country")
    .add(so.Lines())
)

# 전체와 각 그룹의 상태를 동시에 파악
(
    so.Plot(healthexp, x="Year", y="Spending_USD", color="Country")
    .add(so.Area(), so.Stack()) # add stacking
)

(
    so.Plot(healthexp, x="Year", y="Life_Expectancy")
    .add(so.Line(alpha=.3), group="Country", col=None)
    .add(so.Line(linewidth=3))
    .facet("Country", wrap=3)  # wrap!!!
)

fmri = sns.load_dataset("fmri")

p = so.Plot(fmri, x="timepoint", y="signal", color="region", linestyle="event")
p.add(so.Line(), so.Agg())  # Agg()의 default 함수는 mean

p.add(so.Line(marker="o", edgecolor="w"), so.Agg(), linestyle=None)  # linestyle을 overwrite!

Overploting

대표적으로 다음과 같은 방식으로 해결할 수 있음.

  • alpha property: 투명도를 조절
  • so.Jitter() mark: 흐트려뜨려 그리기
  • so.Dots() mark: 불투명, 테두리 선명한 점들
  • .facet() facet: 다른 면에 그리기

특별히 overplotting에 특화된 독립적인 plots도 있음. 예를 들어,

Beeswarm plot: 겹치지 않게 그리기

sns.catplot(
    data=penguins, kind="swarm",
    x="species", y="body_mass_g", hue="sex", col="island",
    height=4.5, aspect=.6
);

2d histogram/hexbin plot: x, y모두 binning하여 상대적 개수를 컬러로 표시

sns.jointplot(penguins, x="bill_length_mm", y="bill_depth_mm", kind="hex", gridsize=30, height=5);  # gridsize: bin 개수

Matplotlib을 이용

plt.figure(figsize=(6, 4), dpi=100)
plt.hexbin(x=penguins["bill_depth_mm"], y=penguins["body_mass_g"], gridsize=30, cmap="Blues")

plt.colorbar()
plt.xlabel("Bill Depth (mm)")
plt.ylabel("Body Mass (g)")
plt.show()

New data

새로운 데이터 값을 이용하고자 할 때, 변수이름 대신 직접 데이터(series, array, list, …) 입력

mpg_suv = mpg.query('`class` == "suv"')

(
    so.Plot(mpg, x="displ", y="hwy")
    .add(so.Dot(), color="class")
    .add(so.Line(), so.PolyFit(5), 
         x=mpg_suv["displ"], y=mpg_suv["hwy"])
)

Tip

원칙적으로 다음과 같이 matplotlib의 스타일로 데이터를 직접 입력해도 됨.

(
    so.Plot()
    .add(so.Dot(), x=mpg["displ"], y=mpg["hwy"], color=mpg["class"])
)

Matplotlib

Matplotlib에 대해서는 다음 교재의 4.Visualization with Matplotlib를 참고

Python Data Science Handbook by Jake VanderPlas

  • 각 변수의 값을 직접 입력: Series나 NumPy array
  • 두 가지 interface를 제공

MATLAB 스타일로 직접 함수를 호출하는 방법

plt.scatter(x=tips["total_bill"], y=tips["tip"])

객체를 만들어서 메서드를 호출하는 방법

fig, ax = plt.subplots()
ax.scatter(x=tips["total_bill"], y=tips["tip"])

Exercises

다음 데이터로 위에서 다룬 시각화를 연습해보세요.

Data on houses in Saratoga County, New York, USA in 2006

houses_data = sm.datasets.get_rdataset("SaratogaHouses", "mosaicData")

houses = houses_data.data   # data
print(houses_data.__doc__)  # description of the data
houses
       price  lotSize  age  landValue  livingArea  pctCollege  bedrooms  \
0     132500     0.09   42      50000         906          35         2   
1     181115     0.92    0      22300        1953          51         3   
2     109000     0.19  133       7300        1944          51         4   
...      ...      ...  ...        ...         ...         ...       ...   
1725  194900     0.39    9      20400        1099          51         2   
1726  125000     0.24   48      16800        1225          51         3   
1727  111300     0.59   86      26000        1959          51         3   

      fireplaces  bathrooms  rooms          heating      fuel  \
0              1       1.00      5         electric  electric   
1              0       2.50      6  hot water/steam       gas   
2              1       1.00      8  hot water/steam       gas   
...          ...        ...    ...              ...       ...   
1725           0       1.00      3          hot air       gas   
1726           1       1.00      7          hot air       gas   
1727           0       1.00      6          hot air       gas   

                  sewer waterfront newConstruction centralAir  
0                septic         No              No         No  
1                septic         No              No         No  
2     public/commercial         No              No         No  
...                 ...        ...             ...        ...  
1725  public/commercial         No              No         No  
1726  public/commercial         No              No         No  
1727             septic         No              No         No  

[1728 rows x 16 columns]