Plots

R for Data Science by Wickham & Grolemund

Author

Sungkyun Cho

Published

October 14, 2024

데이터 분석의 과정


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를 써서 표현
)

Important

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

.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 참고

Tip

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

(
    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

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

Data: palmerpenguins

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))
)

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()
)

Save plots

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

Statistical transformations

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

Important

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

Note

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

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

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

Note

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

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

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

Fitted lines

seaborn.objects

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

right = (
    so.Plot(mpg, x="displ", y="hwy")
    .add(so.Line(), so.PolyFit(5))  # PolyFit(n): n차 다항식으로 fit
)
left = (
    so.Plot(mpg, x="displ", y="hwy", color="drv")  # color mapping이 이후 모든 layer에 적용
    .add(so.Dot())
    .add(so.Line(), so.PolyFit(5))
)

right = (
    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))
)

다른 대안으로는 plotly, seaborn; alternative plots 섹션 참고


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의 변수는 순서가 존재. 
                                # 그렇지 않은 경우 알바벳 순서로. 
).show()  # .show()는 생략해도 됨

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())
).show()

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

# 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축의 카테고리 순서를 변경
).show()

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

A numerical variable

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

left = (
    so.Plot(penguins, x="body_mass_g")
    .add(so.Bars(), so.Hist(binwidth=100))  # binwidth vs. bins
)
right = (
    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
).show()

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

# 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"
).show()

# pandas의 hist() method: 모든 연속 변수에 대해 histogram을 그림
penguins.hist(bins=30)
plt.show() # 생략

Visualizing relationships

A numerical and a categorical variable

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

Boxplot

source: R for Data Science

sns.boxplot(penguins, x="species", y="body_mass_g", fill=False)  # fill: box의 색을 채울지 여부
plt.show()  # 생략해도 무방

(
    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
).show()

(
    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"))  # .Agg(): aggregation, default는 mean
    .add(so.Range(), so.Est(errorbar=("pi", 50)))   # .Range(): 기본 min/max range, 
                                                    # .Est(): estimator
).show()

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())
)

sns.boxplot(penguins, x="species", y="body_mass_g", hue="sex", fill=False)
plt.show() # 생략해도 무방

sns.catplot(
    data=penguins, x="species", y="bill_length_mm", hue="sex", col="island",
    kind="box", fill=False, height=4, aspect=.6,
)
plt.show()

# 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.Bar(), so.Hist(common_bins=False))  # bins을 공유하지 않도록
).show()
# Hist(): 다양한 parameter가 있음...

Frequency polygon

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

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

Density plot

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

Two categorical variables

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

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

Count 대신 proportion을 표시하는 경우

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

# x축 기준으로 normalize
p.add(
    so.Bar(width=.5), so.Hist("proportion", common_norm=["x"]),  # proportion; 
    so.Stack()  # stacking
).show()
# 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
left = p.add(
    so.Bar(width=.5), so.Hist("proportion", common_norm=True),  # common_norm default: True 
    so.Stack()
)

# 각 species별로 normalize 
right = 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()가 유리 
).show()

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))
)

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",
    aspect=.6
)
plt.show()

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 개수
plt.show()

Tip
sns.set_theme(rc={"figure.dpi": 96})

Matplotlib을 이용

plt.figure(figsize=(6, 4), dpi=50)
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

새로운 데이터 값을 이용하고자 할 때, 직접 입력

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에 대해서는 다음 교재의 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

다음 데이터들 중 하나 혹은 둘다로 위에서 다룬 시각화를 연습해보세요.

  1. tips

    tips = sns.load_dataset("tips")
  2. 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