웹 통계를 위해 로지스틱 회귀 분석 코드를 어떻게 작성할까?

 저는 EC2 내부에 저장한 특정 R script를 이용하여 데이터를 전달하고, 그 결과를 내려 합니다.

 이를 위해서 다음과 같은 방식으로 백엔드에서 R script를 실행합니다.

				
					// 특정한 Script를 실행하게 하는 함수 입니다.
function runLogisticForest(
  res,
  filename,
  dependentVar,
  independentVars,
  zerocoding,
  onecoding,
  categoriesInfo
) {
  // R 스크립트 파일의 위치를 지정합니다.
  const scriptPath = "/home/example.R";

  // Node.js의 child_process.spawn을 사용하여 R 스크립트를 비동기적으로 실행합니다.
  // 스크립트에 필요한 모든 파라미터를 명령줄 인수로 전달합니다.
  const rProcess = spawn("Rscript", [
    scriptPath,
    `filename=${filename}`,
    `dependentVar=${dependentVar}`,
    `independentVars=${independentVars}`,
    `zerocoding=${zerocoding}`,
    `onecoding=${onecoding}`,
    `categoriesInfo=${categoriesInfo}`,
  ]);

  // R 프로세스가 종료되면 호출되는 이벤트 핸들러입니다.
  rProcess.on("close", (code) => {
    // R 스크립트의 종료 코드를 로깅합니다.
    console.log(`R process exited with code ${code}`);

    // 종료 코드가 0이 아닌 경우, 스크립트 실행이 실패했다는 것을 의미합니다.
    if (code !== 0) {
      console.error("R script execution failed with code:", code);
      return res.status(500).send("R script execution failed");
    }
    try {
      // 결과 이미지 파일의 경로를 지정합니다.
      const imgPath = "/home/data/result.png";
      // 파일 시스템에서 이미지 파일을 비동기적으로 읽습니다.
      fs.readFile(imgPath, (err, data) => {
        if (err) {
          // 파일 읽기 오류가 발생한 경우, 에러를 로깅하고 클라이언트에 오류 메시지를 전송합니다.
          console.error("Error reading image file:", err);
          return res.status(500).send("Error reading image file");
        }
        // 이미지 데이터를 성공적으로 읽은 경우, HTTP 응답으로 이미지를 전송합니다.
        res.writeHead(200, { "Content-Type": "image/png" });
        res.end(data);
      });
    } catch (err) {
      // 이미지 전송 중 예외가 발생한 경우, 에러를 로깅하고 오류 메시지를 전송합니다.
      console.error("Failed to send image:", err);
      res.status(500).send("Failed to send image");
    }
  });
}
);
				
			

위와 같은 Function 을 Backend.js 파일 내에 지정을 해두는 거죠.

그럼 해당 Function 이 받아야 할 Props에 알맞은 값들을 넣어 실행하면 작동이 됩니다.

여기서는 통계를 통해서 작성된 png 파일의 경로를 res.end 통해서 프론트엔드로 보내주고 있습니다.

다음과 같은 Function 을 사용하기 위해 제가 어떻게 Props 를 받았고,

R 스크립트를 어떻게 작성했는지 서술하겠습니다. 

로지스틱 회귀분석을 위해 필요한 Props

				
					  const rProcess = spawn("Rscript", [
    scriptPath,
    `filename=${filename}`,
    `dependentVar=${dependentVar}`,
    `independentVars=${independentVars}`,
    `zerocoding=${zerocoding}`,
    `onecoding=${onecoding}`,
    `categoriesInfo=${categoriesInfo}`,
  ]);
				
			

저는 다음과 같은 코드로 특정 R script 를 실행하며, 통계에 필요한 정보들을 전달하였습니다.

여기서 각 인자들에 어떤 데이터를 넣어주면 될까요?

 

				
					    //filename 에는 분석에 사용한 CSV 데이터프레임의 경로를 넣습니다. ex."/home/data/mydata.csv"
    `filename=${filename}`
				
			

우선 filename 에는 분석에 사용한 CSV 데이터프레임을 넣어줍니다. 

통계 분석을 위해서 사용할 때 데이터 프레임의 형태로 통계를 돌려줘야 하기 때문에, 이를 위한 데이터프레임 경로를 제공하는 것입니다. 

 

				
					    //로지스틱 회귀 분석에서 사용할 종속 변수의 값을 넣습니다. ex. "sex" (값이 2개인 이진 변수입니다.)
    `dependentVar=${dependentVar}`
				
			

다음은 dependentVar 입니다.

이곳에는 데이터프레임에서 어떤 열을 종속변수로 사용할지 넣어줍니다.

예컨대 성별이 남성일 때는 키, 몸무게, 흡연 유무가 이렇고.. 여성일 때의 키, 몸무게, 흡연 유무는 이런 식으로 차이가 있구나~ 

하는 것을 보고 싶다면… dependentVar 에는 “sex”를 넣어주면 될 것입니다. 

 

 

				
					    //로지스틱 회귀 분석에서 사용할 독립 변수들의 값을 넣습니다. ex. "height,weight,smoking" (,로 구분된 string 입니다.)
    `independentVars=${independentVars}`
				
			

다음은 independentVars 입니다.

앞서 종속변수에서 설명했던 예시를 볼까요?

성별에 따른 키, 몸무게, 흡연 유무가 통계적으로 어떤 상관 관계를 가지는지 알고 싶다는 전제로 설명하겠습니다.

이때 “종속변수” 에는 “키, 몸무게, 흡연 유무” 가 들어가게 됩니다.

즉, 여러 개의 변수가 들어가게 되는데요. 저는 이를 “,” 으로 구분한 string으로 전송했습니다.

예시로 들다면 “height,weight,smoking” 이런 식으로 보낼 수 있겠습니다. 

 

				
					    //종속변수의 값 중 어떤 값을 0으로 사용할지. ex. "female"
    `zerocoding=${zerocoding}`,
    //종속변수의 값 중 어떤 값을 1로 사용할지. ex. "male"
    `onecoding=${onecoding}`
				
			

이번에는 zerocoding 과 onecoding을 보겠습니다.

로지스틱 회귀분석에서 사용하는 종속변수는 이진 변수입니다.

“예, 아니오” “남자, 여자” 와 같이 2개의 값을 가지고 있는 변수라는 뜻입니다.

그리고 회귀 분석의 결과는 0 에서 1로 갈 수록 어떤 독립변수가 얼만큼 증가하여 나타나는지를 보여주게 되죠.

만약 0을 여성, 1을 남성으로 두었을 때 “height” 독립변수의 오즈수가 1.2이라면,

여성에 비해서 남성의 키가 1.2배 정도 더 클 확률이 많다는 거죠.

이를 위해서 우리는 종속변수의 어떤 값을 0으로 설정할지, 어떤 값을 1로 설정할지를 정확히 명시해줄 필요가 있습니다.

만약 우리가 분석하고자 데이터프레임의 종속변수 열의 값이 “male” 또는 “female” 이라면..

분석을 위하여 zerocoding 에는 “female” 을 onecoding 에는 “male” 을 넣어줄 수 있겠습니다.

 

				
					    //종속변수에서 어떤 변수가 "범주형" 변수인지, 그 "범주형" 변수에서 어떤 값을 참조 범주로 삼을지 결정한다. ex. {"smoking" : "yes"}
    `categoriesInfo=${categoriesInfo}`,
				
			

이번에는 categoriesInfo 입니다.

범주형 변수는 여러 가지 범주 또는 그룹으로 나눌 수 있는 변수를 말합니다. 

예를 들어, 성별(남성, 여성), 흡연 여부(예, 아니오) 등이 있습니다. 

참조 범주 (Reference Category)는 로지스틱 회귀 분석에서 계수를 추정할 때 기준점으로 사용되는 범주입니다.

 이 범주에 대한 계수는 0으로 설정되며, 다른 범주들의 계수는 이 참조 범주에 비해 상대적으로 얼마나 확률이 높거나 낮은지를 나타냅니다.

예를 들어, 흡연 여부가 독립 변수로 포함된 로지스틱 회귀 모델을 생각해 보겠습니다. 

흡연 여부 변수(smoking)가 “yes”와 “no”라는 두 가지 범주를 가질 수 있다고 할 때, 만약 “no”를 참조 범주로 설정한다면, 모델은 “yes” 범주가 “no”에 비해 결과 변수(예: 질병 유무)에 미치는 영향을 평가하게 됩니다.

이를 스크립트에 전달하기 위해서 {“smoking” : “no” } 와 같이 나타냈습니다. 

 

이제 필요한 인자들을 모두 알아보았습니다.

그렇다면 이러한 인자들을 정상적으로 받았다는 가정 하에, 어떻게 R 스크립트를 작성하면 될지 알아보겠습니다. 

R 스크립트 작성

				
					# 필요한 패키지 불러오기
library(gridExtra)
library(ggplot2)
library(jsonlite)

# 커맨드라인 인자로부터 파일 이름, 종속 변수, 독립 변수, 코딩 값을 가져오기
args <- commandArgs(trailingOnly = TRUE)

# 인자 파싱
args_list <- list()
for (arg in args) {
  split_arg <- strsplit(arg, "=")[[1]]
  args_list[[split_arg[1]]] <- split_arg[2]
}

# 앞서 설정했던 인자들을 다음과 같이 받아옵니다. 
filename <- args_list$filename
dependentVar <- args_list$dependentVar
independentVars <- args_list$independentVars
zerocoding <- args_list$zerocoding
onecoding <- args_list$onecoding

# 범주형 변수와 참조 범주 정보 파싱
categoriesInfo <- fromJSON(args_list$categoriesInfo)

# 데이터 읽기
data <- read.csv(filename)

variableCoding <- list() # 변수의 핫코딩 정보를 저장할 리스트

# 종속 변수가 적절한 값(0 또는 1)을 갖고 있는지 확인하고 변환
if (dependentVar %in% names(data)) {
  # zerocoding 및 onecoding 값을 0과 1로 매핑
  if (any(data[[dependentVar]] == zerocoding) && any(data[[dependentVar]] == onecoding)) {
    data[[dependentVar]][data[[dependentVar]] == zerocoding] <- 0
    data[[dependentVar]][data[[dependentVar]] == onecoding] <- 1
    data[[dependentVar]] <- as.factor(data[[dependentVar]])
    variableCoding[[dependentVar]] <- list(zerocoding = 0, onecoding = 1) # 핫코딩 정보 추가
  } else {
    stop("Dependent variable must contain the specified zero and one coding values.")
  }
} else {
  stop("Dependent variable not found in the dataset.")
}

# 독립 변수 리스트 생성
vars <- unlist(strsplit(independentVars, split = ","))
if (any(!vars %in% names(data))) {
  stop("One or more independent variables are missing in the dataset.")
}

# 범주형 변수의 참조 범주 설정 (공백 및 빈 값 처리 포함)
for (catVar in names(categoriesInfo)) {
  if (catVar %in% names(data)) {
    # 공백 제거 및 빈 값 처리
    data[[catVar]] <- trimws(data[[catVar]])  # 앞뒤 공백 제거
    data[[catVar]][data[[catVar]] == ""] <- NA  # 빈 값을 NA로 변환
    
    # 참조 범주 값에서 공백 제거
    ref_category <- trimws(categoriesInfo[[catVar]])
    
    # 참조 범주가 실제로 데이터에 존재하는지 확인
    if (ref_category %in% levels(factor(data[[catVar]]))) {
      # 참조 범주 설정
      data[[catVar]] <- relevel(factor(data[[catVar]]), ref = ref_category)
    } else {
      stop(paste("Reference category", ref_category, "for variable", catVar, "not found in the dataset."))
    }
  } else {
    stop(paste("Categorical variable", catVar, "not found in the dataset."))
  }
}


# 로지스틱 회귀 모델 핏팅
fit <- glm(formula = as.formula(paste(dependentVar, "~", paste(vars, collapse = " + "))), data = data, family = binomial())

# coefficients 정보 추출 및 변수명 포함
coefficients_info <- as.data.frame(summary(fit)$coefficients)
# 변수명을 row names에서 추출
coefficients_info$Variable <- row.names(coefficients_info)

# (Intercept) 제거
coefficients_info <- coefficients_info[coefficients_info$Variable != "(Intercept)", ]

# OR 및 95% CI 계산
coefficients_info$OR <- exp(coefficients_info$Estimate)
coefficients_info$CI_Lower <- exp(coefficients_info$Estimate - 1.96 * coefficients_info$`Std. Error`)
coefficients_info$CI_Upper <- exp(coefficients_info$Estimate + 1.96 * coefficients_info$`Std. Error`)

# 결과 데이터 프레임 생성
forest_data <- data.frame(
  Variable = coefficients_info$Variable,
  OR = coefficients_info$OR,
  CI_Lower = coefficients_info$CI_Lower,
  CI_Upper = coefficients_info$CI_Upper,
  CI = paste(sprintf("%.2f", coefficients_info$CI_Lower), ", ", sprintf("%.2f", coefficients_info$CI_Upper), sep = "")
)

# 기본 테이블 설정
table_base <- ggplot(forest_data, aes(y = Variable)) +
  ylab(NULL) + xlab("  ") +
  theme(
    plot.title = element_text(hjust = 0.5, size = 12),
    axis.text.x = element_text(color = "white", hjust = -3, size = 25), # 정렬을 위한 설정
    axis.line = element_blank(),
    axis.text.y = element_blank(),
    axis.ticks = element_blank(),
    axis.title.y = element_blank(),
    legend.position = "none",
    panel.background = element_blank(),
    panel.border = element_blank(),
    panel.grid.major = element_blank(),
    panel.grid.minor = element_blank(),
    plot.background = element_blank()
  )

# OR point estimate table
tab1 <- table_base +
  labs(title = " ") +
  geom_text(aes(y = Variable, x = 1, label = sprintf("%.2f", OR)), size = 4) +
  ggtitle("OR")

# 95% CI table
tab2 <- table_base +
  geom_text(aes(y = Variable, x = 1, label = CI), size = 4) +
  ggtitle("95% CI")

# Forest plot
plot1 <- ggplot(forest_data, aes(x = OR, y = Variable)) +
  geom_point(size = 3, shape = 18, color = "royalblue") +
  geom_errorbarh(aes(xmin = CI_Lower, xmax = CI_Upper), height = 0.2, color = "darkblue") +
  geom_vline(xintercept = 1, linetype = "dotted") +
  xlab("Odds Ratio") +
  theme_minimal()

# 테이블과 플롯을 결합하여 저장
lay <- matrix(c(1, 1, 1, 1, 1, 1, 1, 1, 2, 3, 3), nrow = 1)

# PNG 파일로 저장
png(filename = "/home/result.png", width = 1000, height = 700)
grid.arrange(plot1, tab1, tab2, layout_matrix = lay)
dev.off()

				
			

우선 전체 코드입니다.

너무 길죠?

부분으로 나누어서 보겠습니다. 

 

				
					# 필요한 패키지 불러오기
library(gridExtra)
library(ggplot2)
library(jsonlite)
				
			

우선 가장 앞에서 필요한 패키지들을 불러옵니다.

없으면 SSH 접속해서 R 실행하고 패키지 다운로드 합시다. 

모르시면 아래 버튼 눌러 참고해주세요.

				
					# 커맨드라인 인자로부터 파일 이름, 종속 변수, 독립 변수, 코딩 값을 가져오기
args <- commandArgs(trailingOnly = TRUE)
				
			

요거는 이제 앞서 우리가 해당 R 스크립트 실행을 할 때 아래와 같이 실행을 했었죠?

 

				
					  const rProcess = spawn("Rscript", [
    scriptPath,
    `filename=${filename}`,
    `dependentVar=${dependentVar}`,
    `independentVars=${independentVars}`,
    `zerocoding=${zerocoding}`,
    `onecoding=${onecoding}`,
    `categoriesInfo=${categoriesInfo}`,
  ]);
				
			

위 코드는 사실상 R 스크립트를 아래와 같이 실행시키는 것과 같은 뜻입니다.

				
					Rscript example.R arg1 arg2 arg3

				
			

첫 부분에 있는 scriptPath 를 제외하고 나머지 값들이 arg1, arg2 .. 이런 식으로 설정이 된다는 뜻이 됩니다.


				
					# 인자 파싱
args_list <- list()
for (arg in args) {
  split_arg <- strsplit(arg, "=")[[1]]
  args_list[[split_arg[1]]] <- split_arg[2]
}
				
			

빈 리스트 args_list를 생성합니다. 이 리스트는 각 인자를 key=value 형식으로 저장할 것입니다.

args 벡터의 각 요소에 대해 반복 작업을 수행합니다.

arg는 args 벡터의 각 요소를 나타냅니다. 예를 들어, filename=mydata.csv 같은 문자열입니다.

strsplit(arg, “=”) 함수는 arg 문자열을 = 문자를 기준으로 나눕니다. 결과는 리스트 형태로 반환되며, 이 리스트의 첫 번째 요소([[1]])를 사용하여 벡터를 얻습니다.

split_arg[1]은 key(“filename”)가 되고, split_arg[2]은 value(“mydata.csv”)가 됩니다.

예를 들어, args_list[[“filename”]]는 “mydata.csv”가 됩니다.

코드 예제로 쭉 본다면

				
					Rscript script.R filename=mydata.csv dependentVar=sex independentVars=height,weight zerocoding=female onecoding=male

				
			
				
					args <- c("filename=mydata.csv", "dependentVar=sex", "independentVars=height,weight", "zerocoding=female", "onecoding=male")


				
			
				
					args_list <- list(
  filename = "mydata.csv",
  dependentVar = "sex",
  independentVars = "height,weight",
  zerocoding = "female",
  onecoding = "male"
)

				
			
				
					filename <- args_list$filename
dependentVar <- args_list$dependentVar
independentVars <- args_list$independentVars
zerocoding <- args_list$zerocoding
onecoding <- args_list$onecoding


				
			

위와 같은 순서대로 내려와서 결과적을 R 코드내에서  각 값이 특정한 변수에 저장 가능하단 뜻입니다.

				
					

# 범주형 변수와 참조 범주 정보 파싱
categoriesInfo <- fromJSON(args_list$categoriesInfo)


				
			

다만 여기서 단순히 특정 변수에 넣지 않고, categoriesInfo 는 json 타입으로 파싱을 해주겠습니다.

{ key : value } 형태를 가진 변수니까요.

				
					# 데이터 읽기
data <- read.csv(filename)

variableCoding <- list() # 변수의 핫코딩 정보를 저장할 리스트

# 종속 변수가 적절한 값(0 또는 1)을 갖고 있는지 확인하고 변환
if (dependentVar %in% names(data)) {
  # zerocoding 및 onecoding 값을 0과 1로 매핑
  if (any(data[[dependentVar]] == zerocoding) && any(data[[dependentVar]] == onecoding)) {
    data[[dependentVar]][data[[dependentVar]] == zerocoding] <- 0
    data[[dependentVar]][data[[dependentVar]] == onecoding] <- 1
    data[[dependentVar]] <- as.factor(data[[dependentVar]])
    variableCoding[[dependentVar]] <- list(zerocoding = 0, onecoding = 1) # 핫코딩 정보 추가
  } else {
    stop("Dependent variable must contain the specified zero and one coding values.")
  }
} else {
  stop("Dependent variable not found in the dataset.")
}

				
			

이제 지정했던 경로를 이용해서 CSV 를 읽고 데이터프레임을 data로 지정합니다.

그 후 variableCoding 이라는 빈 리스트를 하나 만듭시다.

이 빈 리스트에는 마지막에 어떤 값을 0으로, 어떤 값을 1로 만들었는지에 대한 문자열이 들어갑니다. 

만약 data 의 열 중에서 종속 변수에 해당하는 열이 존재한다면, 종속 변수의 두 값을 0과 1로 바꿔주는 작업을 시행합니다.

이후, 한 번 더 검증에 들어갑니다.

“sex” 를 종속변수로 선언하고, “male” 을 1로 “female”을 0으로 설정하기로 가정했다면,

data 내에 “sex” 라는 열에 두 값이 모두 존재하는지 확인하고, 있다면 다음 단계로 넘어갑니다.

이후 data 내에서 “sex” 라는 열 내에서 male 을 1로, female 을 0으로 뒤바꾸게 됩니다. 

data[[dependentVar]] <- as.factor(data[[dependentVar]])

이 작업은 data 라는 데이터 프레임에서 해당하는 열의 값이 종속형 변수임을 지정하는 로직입니다.
				
					# 독립 변수 리스트 생성
vars <- unlist(strsplit(independentVars, split = ","))
if (any(!vars %in% names(data))) {
  stop("One or more independent variables are missing in the dataset.")
}

# 범주형 변수의 참조 범주 설정 (공백 및 빈 값 처리 포함)
for (catVar in names(categoriesInfo)) {
  if (catVar %in% names(data)) {
    # 공백 제거 및 빈 값 처리
    data[[catVar]] <- trimws(data[[catVar]])  # 앞뒤 공백 제거
    data[[catVar]][data[[catVar]] == ""] <- NA  # 빈 값을 NA로 변환
    
    # 참조 범주 값에서 공백 제거
    ref_category <- trimws(categoriesInfo[[catVar]])
    
    # 참조 범주가 실제로 데이터에 존재하는지 확인
    if (ref_category %in% levels(factor(data[[catVar]]))) {
      # 참조 범주 설정
      data[[catVar]] <- relevel(factor(data[[catVar]]), ref = ref_category)
    } else {
      stop(paste("Reference category", ref_category, "for variable", catVar, "not found in the dataset."))
    }
  } else {
    stop(paste("Categorical variable", catVar, "not found in the dataset."))
  }
}


				
			

독립변수를 받을 때 “A,B,C” 처럼 문자열로 받되 , 으로 각 값을 구분했습니다.

이것을 strsplit method 를 이용해서 특정 문자열을 , 으로 구분합니다.

그럼 이게 리스트로 변신합니다. 리스트의 이름은 vars 로 지정합니다.

vars 가 data 의 열에 들어가는지 한 번 확인해주고, 범주형 변수의 참조 변수 설정으로 넘어갑니다.

names(categoriesInfo) 의 경우는 객체 형태의 정보에서 Key 값 만을 리스트로 바꾼 겁니다.

즉 catVar 이 각각의 key 값이 되어 순회하게 됩니다.

만약 정보의 key 값이 data 의 열에 존재한다면, 해당 열의 값들의 공백과 NA 값을 처리합니다.

그리고 trimws 방법으로 key 값에 해당하는 value. 즉, 참조변수로 사용하기 위한 값도 공백 처리를 하여 ref_category 로 지정을 합니다.

이후 factor 이라는 method 를 이용해 해당하는 열의 값들은 범주형 데이터임을 data 내부에 기억하도록 합니다.

 

 

				
					# 로지스틱 회귀 모델 핏팅
fit <- glm(formula = as.formula(paste(dependentVar, "~", paste(vars, collapse = " + "))), data = data, family = binomial())



				
			

그럼 이제 필요한 데이터들이 모였습니다. 

glm 함수는 R에서 일반화 선형 모델을 적합하는 작업을 수행합니다.

as.formula 라는 함수를 넣어줍니다. 회귀 분석 모델을 정의하는 데 사용됩니다.

pastate 함수는 여러 문자열을 하나의 문자열로 합하는 역할을 합니다. 

glm(formula = as.formula(…), data = data, family = binomial())

결과적으로 … 부분에 (종속변수) ~ c(독립변수들) 이 들어가게 됩니다.
 
그럼 나머지 설정 값들은 간단합니다.
 
data 부분에는 말 그대로 사용할 데이터프레임을 넣어줍시다.
 
family = binomial(): 이 옵션은 로지스틱 회귀 모델을 적합시키기 위해 이항 분포를 사용하도록 지정합니다.
 

 

				
					# 범주형 변수의 이름 추출
categorical_vars <- names(categoriesInfo)

# coefficients 정보 추출 및 변수명 포함
coefficients_info <- as.data.frame(summary(fit)$coefficients)
# 변수명을 row names에서 추출
coefficients_info$Variable <- row.names(coefficients_info)

# (Intercept) 제거
coefficients_info <- coefficients_info[coefficients_info$Variable != "(Intercept)", ]

# 범주형 변수의 이름만 남기기
coefficients_info$Variable <- sapply(coefficients_info$Variable, function(var) {
  for (catVar in categorical_vars) {
    if (startsWith(var, catVar)) {
      return(catVar)
    }
  }
  return(var)
})

# OR 및 95% CI 계산
coefficients_info$OR <- exp(coefficients_info$Estimate)
coefficients_info$CI_Lower <- exp(coefficients_info$Estimate - 1.96 * coefficients_info$`Std. Error`)
coefficients_info$CI_Upper <- exp(coefficients_info$Estimate + 1.96 * coefficients_info$`Std. Error`)

# 중복 제거 (범주형 변수의 여러 레벨이 있는 경우 첫 번째만 남김)
coefficients_info <- coefficients_info[!duplicated(coefficients_info$Variable), ]

# 결과 데이터 프레임 생성
forest_data <- data.frame(
  Variable = coefficients_info$Variable,
  OR = coefficients_info$OR,
  CI_Lower = coefficients_info$CI_Lower,
  CI_Upper = coefficients_info$CI_Upper,
  CI = paste(sprintf("%.2f", coefficients_info$CI_Lower), ", ", sprintf("%.2f", coefficients_info$CI_Upper), sep = "")
)
				
			

그럼 이후 계산은 간단합니다.

summary(fit) 에서 coefficients 에 해당하는 정보만을 가져오며 로지스틱 회귀 분석 결과를 낼 수 있는 정보들을 가져오기가 가능합니다.

이것을 이용하여 OR 및 CI 를 계산해 주고 내가 원하는 정보만을 넣어 데이터 프레임 구성이 가능합니다.

 

디자인

				
					# 기본 테이블 설정
table_base <- ggplot(forest_data, aes(y = Variable)) +
  ylab(NULL) + xlab("  ") +
  theme(
    plot.title = element_text(hjust = 0.5, size = 12),
    axis.text.x = element_text(color = "white", hjust = -3, size = 25), # 정렬을 위한 설정
    axis.line = element_blank(),
    axis.text.y = element_blank(),
    axis.ticks = element_blank(),
    axis.title.y = element_blank(),
    legend.position = "none",
    panel.background = element_blank(),
    panel.border = element_blank(),
    panel.grid.major = element_blank(),
    panel.grid.minor = element_blank(),
    plot.background = element_blank()
  )

# OR point estimate table
tab1 <- table_base +
  labs(title = " ") +
  geom_text(aes(y = Variable, x = 1, label = sprintf("%.2f", OR)), size = 4) +
  ggtitle("OR")

# 95% CI table
tab2 <- table_base +
  geom_text(aes(y = Variable, x = 1, label = CI), size = 4) +
  ggtitle("95% CI")

# Forest plot
plot1 <- ggplot(forest_data, aes(x = OR, y = Variable)) +
  geom_point(size = 3, shape = 18, color = "royalblue") +
  geom_errorbarh(aes(xmin = CI_Lower, xmax = CI_Upper), height = 0.2, color = "darkblue") +
  geom_vline(xintercept = 1, linetype = "dotted") +
  xlab("Odds Ratio") +
  theme_minimal()

# 테이블과 플롯을 결합하여 저장
lay <- matrix(c(1, 1, 1, 1, 1, 1, 1, 1, 2, 3, 3), nrow = 1)

# PNG 파일로 저장
png(filename = "/home/ec2-user/data/forest_plot.png", width = 1000, height = 700)
grid.arrange(plot1, tab1, tab2, layout_matrix = lay)
dev.off()

				
			

구성한 정보를 이용하여 테이블을 어떻게 디자인을 할 지는 사실 서핑의 영역입니다.

저는 위 디자인을 다음 페이지에서 골라 사용했습니다.

바로가기

 

마치며...

비록 더미 데이터이기는 하지만,

0을 남자로, 1을 여자로 두었을 때 다음과 같이 결과가 나타는 것을 볼 수 있습니다.

당연히 키 몸무게 모두 여자로 갈수록 남자에 비해 낮아진다는 것을 한 눈에 볼 수 있습니다. 

이런 png 파일을 내 EC2 특정 공간에 저장할 수 있게 됩니다.