본문 바로가기
R 프로그래밍/R 데이터 시각화

[R 그래픽스] 평균(means)과 오차(error) 막대그래프 그리기

by 찐남 2022. 1. 8.
본 포스팅은 R Graphics Cookbook을 기반으로 하여 작성하였습니다.

 

ggplot2로 그래프를 만들려면

데이터가 데이터 프레임으로 존재해야 하며,

데이터 형식이 "long format"이어야 합니다.

데이터를 재구성해야 하는 경우 자세한 내용은 여기를 참조하세요.

 

 


샘플 데이터

아래 예는 ToothGrowth 데이터 세트예요.

복용량(dose)은 숫자 형식의 열이기 때문에 그것을 factor로 변환하는 것이 유용할 수 있어요.

tg <- ToothGrowth
head(tg)

library(ggplot2) # ggplot2 패키지 로드

우선, 데이터를 요약할 필요가 있어요.

이 페이지에 설명된 대로 여러 가지 방법으로 이 작업을 수행할 수 있은데,

이 경우 해당 페이지와 이 페이지 하단에 정의된 summarySE() 함수를 사용하셔야 합니다.

(※ summarySE 함수는 사용자 정의 함수로 R에서 제공하는 함수가 아니에요.

이 함수에 대한 자세한 설명은 여기를 참고하시면 돼요.

우선, 아래 코드를 일단 실행시켜 summarySE 함수를 사용할 준비를 할게요.)

summarySE <- function(data=NULL, measurevar, groupvars=NULL, na.rm=FALSE,
                      conf.interval=.95, .drop=TRUE) {
    library(plyr)

    length2 <- function (x, na.rm=FALSE) {
        if (na.rm) sum(!is.na(x))
        else       length(x)
    }

    datac <- ddply(data, groupvars, .drop=.drop,
      .fun = function(xx, col) {
        c(N    = length2(xx[[col]], na.rm=na.rm),
          mean = mean   (xx[[col]], na.rm=na.rm),
          sd   = sd     (xx[[col]], na.rm=na.rm)
        )
      },
      measurevar
    )

    datac <- rename(datac, c("mean" = measurevar))
    datac$se <- datac$sd / sqrt(datac$N)  # Calculate standard error of the mean



    ciMult <- qt(conf.interval/2 + .5, datac$N-1)
    datac$ci <- datac$se * ciMult

    return(datac)
}

 

# 사용자 정의 함수 summarySE 는 표준 편차, 평균의 표준 오차 및 (기본값 95%) 신뢰 구간 산출
tgc <- summarySE(tg, measurevar="len", groupvars=c("supp","dose"))
tgc 

 

선 그래프

데이터를 요약한 후에 그래프를 만들어 표현할 수 있어요.

아래는 평균의 표준 오차 또는 95% 신뢰 구간을 나타내는 오차 막대가 있는 기본 선 및 점 그래프입니다.

# 평균의 표준 오차
ggplot(tgc, aes(x=dose, y=len, colour=supp)) + 
    geom_errorbar(aes(ymin=len-se, ymax=len+se), width=.1) +
    geom_line() +
    geom_point()

# 오차 막대가 겹치지 않게 position_dodge를 사용하여 수평으로 이동
pd <- position_dodge(0.1) # 왼쪽과 오른쪽으로 0.05 이동

ggplot(tgc, aes(x=dose, y=len, colour=supp)) + 
    geom_errorbar(aes(ymin=len-se, ymax=len+se), width=.1, position=pd) +
    geom_line(position=pd) +
    geom_point(position=pd)

# SEM 대신 95% 신뢰 구간 사용
ggplot(tgc, aes(x=dose, y=len, colour=supp)) + 
    geom_errorbar(aes(ymin=len-ci, ymax=len+ci), width=.1, position=pd) +
    geom_line(position=pd) +
    geom_point(position=pd)

# 검은색 오류 막대 - 'group=supp' 매핑을 확인합니다. -- 없으면 오류가 발생합니다.
ggplot(tgc, aes(x=dose, y=len, colour=supp, group=supp)) + 
    geom_errorbar(aes(ymin=len-ci, ymax=len+ci), colour="black", width=.1, position=pd) +
    geom_line(position=pd) +
    geom_point(position=pd, size=3)

 

평균의 표준 오차를 나타내는 오차 막대가 있는 완성된 그래프는 아래에 있어요.

흰색 채우기가 선과 오차 막대 위에 오도록 점을 마지막에 그렸어요.

ggplot(tgc, aes(x=dose, y=len, colour=supp, group=supp)) + 
    geom_errorbar(aes(ymin=len-se, ymax=len+se), colour="black", width=.1, position=pd) +
    geom_line(position=pd) +
    geom_point(position=pd, size=3, shape=21, fill="white") + # shape =21 은 색상으로 가득 채워진 원
    xlab("Dose (mg)") +
    ylab("Tooth length") +
    scale_colour_hue(name="Supplement type",    # 범례 레이블, 더 어두운 색상 사용
                     breaks=c("OJ", "VC"),
                     labels=c("Orange juice", "Ascorbic acid"),
                     l=40) +                    # 더 어두운 색상 사용, 밝기=40
    ggtitle("The Effect of Vitamin C on\nTooth Growth in Guinea Pigs") +
    expand_limits(y=0) +                        # y 범위 확장
    scale_y_continuous(breaks=0:20*4) +         # 4 단위마다 틱 설정
    theme_bw() +
    theme(legend.justification=c(1,0),
          legend.position=c(1,0))               # 오른쪽 하단으로 범례 위치 조정

 

 

막대그래프

절차는 일반적인 막대그래프와 유사해요.

tgc$dose는 factor형이어야 해요. 숫자형 벡터인 경우 작동하지 않으니, 주의하세요.

# dose을 factor형 변수로 변환
tgc2 <- tgc
tgc2$dose <- factor(tgc2$dose)
str(tgc2)

# 오차 막대는 평균의 표준 오차를 표현
ggplot(tgc2, aes(x=dose, y=len, fill=supp)) + 
    geom_bar(position=position_dodge(), stat="identity") +
    geom_errorbar(aes(ymin=len-se, ymax=len+se),
                  width=.2,                    # 오차 막대의 너비 조정
                  position=position_dodge(.9))

# SEM 대신 95% 신뢰 구간 사용
ggplot(tgc2, aes(x=dose, y=len, fill=supp)) + 
    geom_bar(position=position_dodge(), stat="identity") +
    geom_errorbar(aes(ymin=len-ci, ymax=len+ci),
                  width=.2,                    # 오차 막대의 너비 조정
                  position=position_dodge(.9))

 

최종 완성된 그래프는 아래와 같습니다.

ggplot(tgc2, aes(x=dose, y=len, fill=supp)) + 
    geom_bar(position=position_dodge(), stat="identity",
             colour="black",    # 막대그래프에 검은색 윤곽선 사용,
             size=.3) +           # 얇은 선
    geom_errorbar(aes(ymin=len-se, ymax=len+se),
                  size=.3,    # 얇은 선
                  width=.2,
                  position=position_dodge(.9)) +
    xlab("Dose (mg)") +
    ylab("Tooth length") +
    scale_fill_hue(name="Supplement type", # 범례 라벨, 더 어두운 색상 사용
                   breaks=c("OJ", "VC"),
                   labels=c("Orange juice", "Ascorbic acid")) +
    ggtitle("The Effect of Vitamin C on\nTooth Growth in Guinea Pigs") +
    scale_y_continuous(breaks=0:20*4) +
    theme_bw()

 

개체 내 변수에 대한 오차 막대

위에서 보신 바와 같이 모든 변수가 개체 사이에 있을 때 표준 오차 또는 신뢰 구간을 그리는 것은 간단해요.

그러나 개체 내에 변수(반복 측정)가 있는 경우 표준 오차 또는 정규 신뢰 구간을 표시하면,

조건 간의 차이에 대한 추론이 잘못될 수 있어요.

 

아래 방법은 Cousineau(2005)를 수정한 Morey(2008)에서 가져온 것으로

Loftus와 Masson(1994)에서보다 더 간단한 방법이에요.

개체 내 변수가 있는 오차 막대와 관련된 문제에 대한 보다 상세한 내용은 넘어가도록 할게요.

 

<하나의 개체 내 변수>

다음은 하나의 개체 내 변수(사전/사후 테스트)가 있는 데이터 세트(Morey 2008)에요.

dfw <- read.table(header=TRUE, text='
 subject pretest posttest
       1    59.4     64.5
       2    46.4     52.4
       3    46.0     49.7
       4    49.0     48.7
       5    32.5     37.4
       6    45.2     49.5
       7    60.3     59.9
       8    54.3     54.1
       9    45.4     49.6
      10    38.9     48.5
 ')

# subject를 factor형으로 변환
dfw$subject <- factor(dfw$subject)

 

첫 번째 단계는 데이터 구조를 long format으로 변환하는 것이요.

데이터 구조 변환에 대한 자세한 내용은 여기를 보세요

# long format으로 변환
library(reshape2)
dfw_long <- melt(dfw,
                 id.vars = "subject",
                 measure.vars = c("pretest","posttest"),
                 variable.name = "condition")

dfw_long

 

그래프로 표현하기 위해서 summarySEwithin 함수를 사용하여 데이터를 축약합니다.

(summarySEwithin 함수는 사용자 정의 함수로 R에 내장되어 있는 함수가 아니에요.

아래 2개의 사용자 정의 함수를 먼저 실행해야 합니다. 처음에는 다소 어려울 수 있으니, 실행만 하시고 넘어가세요.)

## 데이터 프레임에서 지정된 그룹 내의 데이터 규범화
## 이는 betweenvars로 지정된 각 그룹 내에서 동일한 평균값을 갖도록 각 개체(idvar로 식별됨)를 정규화
##   data: 데이터 프레임
##   idvar: 각 개체(또는 일치하는 개체)를 식별하는 열의 이름
##   measurevar: 요약할 변수를 포함하는 열의 이름
##   betweenvars: 개체 간 변수인 열의 이름을 포함하는 벡터
##   na.rm: NA를 무시할지 여부를 나타내는 부울
normDataWithin <- function(data=NULL, idvar, measurevar, betweenvars=NULL,
                           na.rm=FALSE, .drop=TRUE) {
    library(plyr)

    # 왼쪽의 var, 오른쪽의 var 사이의 idvar +를 측정
    data.subjMean <- ddply(data, c(idvar, betweenvars), .drop=.drop,
     .fun = function(xx, col, na.rm) {
        c(subjMean = mean(xx[,col], na.rm=na.rm))
      },
      measurevar,
      na.rm
    )

    # 원본 데이터와 함께 개체 평균값 삽입
    data <- merge(data, data.subjMean)

    # 새 열에서 정규화된 데이터 가져오기
    measureNormedVar <- paste(measurevar, "_norm", sep="")
    data[,measureNormedVar] <- data[,measurevar] - data[,"subjMean"] +
                               mean(data[,measurevar], na.rm=na.rm)

    # 개체 평균 열 제거
    data$subjMean <- NULL

    return(data)
}
## 데이터를 요약하고 개체 간 변동성을 제거하여 개체 내 변수 처리
## 내부 S 변수가 없는 경우에도 여전히 작동
## 개수, 정규화되지 않은 평균, 정규화된 평균(그룹 간 평균이 동일함),
## 표준 편차, 평균의 표준 오차 및 신뢰 구간 제공
## 개체 내 변수가 있는 경우 Morey(2008)의 방법을 사용하여 수정된 값 계산
##   data: 데이터 프레임
##   measurevar: 요약할 변수를 포함하는 열의 이름
##   betweenvars: 개체 간 변수인 열의 이름을 포함하는 벡터
##   withinvars: 개체 내 변수인 열의 이름을 포함하는 벡터
##   idvar: 각 주제(또는 일치하는 주제)를 식별하는 열의 이름
##   na.rm: NA를 무시할지 여부를 나타내는 부울
##   conf.interval: 신뢰 구간의 백분율 범위(기본값은 95%)

summarySEwithin <- function(data=NULL, measurevar, betweenvars=NULL, withinvars=NULL,
                            idvar=NULL, na.rm=FALSE, conf.interval=.95, .drop=TRUE) {

  # betweenvars 및 insidevars가 요인인지 확인
  factorvars <- vapply(data[, c(betweenvars, withinvars), drop=FALSE],
    FUN=is.factor, FUN.VALUE=logical(1))

  if (!all(factorvars)) {
    nonfactorvars <- names(factorvars)[!factorvars]
    message("Automatically converting the following non-factors to factors: ",
            paste(nonfactorvars, collapse = ", "))
    data[nonfactorvars] <- lapply(data[nonfactorvars], factor)
  }

  # 정규화되지 않은 데이터에서 평균값 가져오기
  datac <- summarySE(data, measurevar, groupvars=c(betweenvars, withinvars),
                     na.rm=na.rm, conf.interval=conf.interval, .drop=.drop)

  # 사용하지 않는 모든 열 삭제(표준 데이터로 계산됨)
  datac$sd <- NULL
  datac$se <- NULL
  datac$ci <- NULL

  # 각 개체의 데이터를 규범화
  ndata <- normDataWithin(data, idvar, measurevar, betweenvars, na.rm, .drop=.drop)

  # 새 열의 이름
  measurevar_n <- paste(measurevar, "_norm", sep="")

  # 정규화된 데이터 축소
  # var 사이 및 var 내에서 동일하게 처리 가능
  ndatac <- summarySE(ndata, measurevar_n, groupvars=c(betweenvars, withinvars),
                      na.rm=na.rm, conf.interval=conf.interval, .drop=.drop)

  # 표준 오차 및 신뢰 구간에 Morey(2008)의 보정 적용
  #  S 내 변수의 조건 수의 곱 구하기
  nWithinGroups    <- prod(vapply(ndatac[,withinvars, drop=FALSE], FUN=nlevels,
                           FUN.VALUE=numeric(1)))
  correctionFactor <- sqrt( nWithinGroups / (nWithinGroups-1) )

  # 보정 계수 적용
  ndatac$sd <- ndatac$sd * correctionFactor
  ndatac$se <- ndatac$se * correctionFactor
  ndatac$ci <- ndatac$ci * correctionFactor

  # 정규화되지 않은 평균값을 정규화된 결과와 결합
  merge(datac, ndatac)
}

 

그래프로 표현하기 위해서 summarySEwithin 함수를 사용하여 데이터를 축약합니다.

dfwc <- summarySEwithin(dfw_long, measurevar="value", withinvars="condition",
                        idvar="subject", na.rm=FALSE, conf.interval=.95)

dfwc

library(ggplot2)
# 95% 신뢰구간으로 그래프 만들기
ggplot(dfwc, aes(x=condition, y=value, group=1)) +
    geom_line() +
    geom_errorbar(width=.1, aes(ymin=value-ci, ymax=value+ci)) +
    geom_point(shape=21, size=3, fill="white") +
    ylim(40,60)

value 및 value_norm 열은 비정규 및 정규 평균을 나타냅니다. 

자세한 내용은 아래의 표준 평균 섹션을 참조하십시오.

 

 


<개체 내 오차 막대 이해하기>

이 섹션에서는 개체 내 오차 막대 값이 계산되는 방법을 설명드릴게요.

여기 단계는 설명을 위한 것이라, 오차 막대를 만드는 데는 필요하지 않아요.

 

개별 데이터의 그래프는 개체 내 변수 조건에 대해 일관된 경향이 있음을 보여주지만,

각 그룹에 대한 규칙적인 표준 오차(또는 신뢰 구간)를 취함으로써 반드시 밝혀지는 것은 아닙니다.

Morey(2008)와 Cousineau(2005)의 방법은 기본적으로 데이터를 정규화하여 개체 간 변동성을 제거하고,

이 정규화된 데이터에서 분산을 계산합니다.

# 일관된 y 범위 사용
ymax <- max(dfw_long$value)
ymin <- min(dfw_long$value)

# 개별 플롯
ggplot(dfw_long, aes(x=condition, y=value, colour=subject, group=subject)) +
    geom_line() + geom_point(shape=21, fill="white") + 
    ylim(ymin,ymax)

# 데이터의 표준 버전 생성
dfwNorm.long <- normDataWithin(data=dfw_long, idvar="subject", measurevar="value")

# 정규화된 개별 개체 플로팅
ggplot(dfwNorm.long, aes(x=condition, y=value_norm, colour=subject, group=subject)) +
    geom_line() + geom_point(shape=21, fill="white") + 
    ylim(ymin,ymax)

 

일반 방법과 개체 내 방법에 대한 오차 막대의 차이가 여기에 나와 있어요.

일반 오차 막대는 빨간색으로, 개체 내 오차 막대는 검은색으로 표시했어요.

# summarySEwithin 대신에 조건을 개체 간 변수인 것처럼 취급하는 summarySE를 사용
dfwc_between <- summarySE(data=dfw_long, measurevar="value", groupvars="condition", na.rm=FALSE, conf.interval=.95)
dfwc_between

# 사이 S CI는 빨간색으로, 내부 S CI는 검은색으로 표시
ggplot(dfwc_between, aes(x=condition, y=value, group=1)) +
    geom_line() +
    geom_errorbar(width=.1, aes(ymin=value-ci, ymax=value+ci), colour="red") +
    geom_errorbar(width=.1, aes(ymin=value-ci, ymax=value+ci), data=dfwc) +
    geom_point(shape=21, size=3, fill="white") +
    ylim(ymin,ymax)

 


<두 개의 개체 내 변수>

개체 내 변수가 두 개 이상인 경우 동일한 함수인 summarySEwithin을 사용할 수 있어요.

이 데이터 세트는 Hays(1994)에서 가져왔으며, Rouder와 Morey(2005)에서

이러한 유형의 개체 내 오차 막대를 만드는 데 사용되었어요.

data <- read.table(header=TRUE, text='
 Subject RoundMono SquareMono RoundColor SquareColor
       1        41         40         41          37
       2        57         56         56          53
       3        52         53         53          50
       4        49         47         47          47
       5        47         48         48          47
       6        37         34         35          36
       7        47         50         47          46
       8        41         40         38          40
       9        48         47         49          45
      10        37         35         36          35
      11        32         31         31          33
      12        47         42         42          42
')

 

먼저 데이터를 long format으로 변환해야 해요.

이 경우 열 이름은 모양(원/사각형)과 색 구성표(단색/색상)의 두 가지 변수를 나타냅니다.

# long format으로 변환
library(reshape2)

data_long <- melt(data=data, id.var="Subject",
                  measure.vars=c("RoundMono", "SquareMono", "RoundColor", "SquareColor"),
                  variable.name="Condition")
names(data_long)[names(data_long)=="value"] <- "Time"

# 조건 열을 Shape 및 ColorScheme으로 분할
data_long$Shape <- NA
data_long$Shape[grepl("^Round",  data_long$Condition)] <- "Round"
data_long$Shape[grepl("^Square", data_long$Condition)] <- "Square"
data_long$Shape <- factor(data_long$Shape)

data_long$ColorScheme <- NA
data_long$ColorScheme[grepl("Mono$",  data_long$Condition)] <- "Monochromatic"
data_long$ColorScheme[grepl("Color$", data_long$Condition)] <- "Colored"
data_long$ColorScheme <- factor(data_long$ColorScheme, levels=c("Monochromatic","Colored"))

# 조건 열 제거
data_long$Condition <- NULL

# 처음 몇 행 보기 
head(data_long)

 

이제 요약하고 그래프로 나타낼 수 있어요.

datac <- summarySEwithin(data_long, measurevar="Time", withinvars=c("Shape","ColorScheme"), idvar="Subject")
datac

library(ggplot2)
ggplot(datac, aes(x=Shape, y=Time, fill=ColorScheme)) +
    geom_bar(position=position_dodge(.9), colour="black", stat="identity") +
    geom_errorbar(position=position_dodge(.9), width=.25, aes(ymin=Time-ci, ymax=Time+ci)) +
    coord_cartesian(ylim=c(40,46)) +
    scale_fill_manual(values=c("#CCCCCC","#FFFFFF")) +
    scale_y_continuous(breaks=seq(1:100)) +
    theme_bw() +
    geom_hline(yintercept=38) 

 

정규화된 평균에 대한 참고 사항

summarySEWithin 함수는 정규 및 비정규 평균을 모두 반환합니다.

정규화되지 않은 평균은 단순히 각 그룹의 평균이에요.

 

정규화된 평균은 각 개체 간 그룹의 평균이 동일하도록 계산되고,

이러한 값은 개체 간 변수가 있을 때 발산할 수 있습니다.

 

예를 들면, 아래와 같은 경우죠.

dat <- read.table(header=TRUE, text='
id trial gender dv
 A     0   male  2
 A     1   male  4
 B     0   male  6
 B     1   male  8
 C     0 female 22
 C     1 female 24
 D     0 female 26
 D     1 female 28
')

# 정규화된 평균과 비 정규화된 평균은 다름
summarySEwithin(dat, measurevar="dv", withinvars="trial", betweenvars="gender", idvar="id")

반응형

댓글