LLM을 이용한 분석 report 생성 (1)

LLM을 이용하여 표 또는 그래프를 요약하는 Quarto 문서를 만들어 봅시다.

R
API
Documentation
Author
Published

October 14, 2024

Introduction

Quarto는 오픈소스 기반의 과학 및 기술 출판 시스템입니다.

코드를 기반으로 문서를 생성하기 때문에 동일한 형식의 데이터으로부터 일정한 결과물을 얻을 수 있습니다.

이는 문서의 일관성과 재사용성을 증대시키고 사람에 의한 실수를 줄이는 데 큰 도움이 됩니다.

그러나 문서의 모든 요소를 코드의 형태로 만들 수 있는것은 아닙니다.

이를테면 문서에 포함된 표 또는 그래프에 대한 설명은 작성자가 직접 작성해야합니다.

이 과정을 효율적으로 해결하기 위해서 차라투에서는 LLM을 활용했습니다.

Prerequisites

Libraries

httr2

OpenAI endpoint에 GET 요청을 실행하기 위한 라이브러리

ggplot2

그래프를 그리기 위한 라이브러리

base64enc

이미지를 base64로 인코딩 하기 위한 라이브러리

flextable

복잡한 table을 만들기 위한 라이브러리

officer

flextable을 html format으로 변환할 때 사용되는 라이브러리

magrittr

파이프 연산자를 사용하기 위한 라이브러리

DBI (Optional)

DB와 통신하기 위한 라이브러리

RSQLite (Optional)

Sqlite3 인터페이스 라이브러리

digest (Optional)

해쉬 알고리즘을 위한 라이브러리

OpenAI api key

본 과정에서는 OpenAI 사의 GPT4o-mini 모델을 활용했습니다.

본인의 OpenAI api 키를 발급받을 경우 동일한 방식으로 실습을 진행하실 수 있습니다.

https://openai.com/index/openai-api/

openai_key <- "your openAI key"
endpoint <- "https://api.openai.com/v1/"

Features

Instruction

OpenAI endpoint로 요청 시 Text parameter가 포함 됩니다.

이 Text에는 작업 지시 사항인 Instruction과 분석할 데이터가 포함됩니다.

Instruction의 예시는 다음과 같습니다.

instruction <- "지침:
- 한글로 4문장 이상 답변하세요.
- 표에 있는 범주별로 내용을 설명하세요.
- 각 범주에 포함된 모든 항목에 대해 설명하세요."

Request

Endpoint(https://api.openai.com/v1/chat/completions)로 요청 시 다음의 parameter가 사용되었습니다.

  • Header
    • Content-Type: application/json
    • Authorization: Bearer $OPENAI_API_KEY
  • Body
    • model(*): gpt-4o-mini [openAI의 다른 모델도 사용할 수 있습니다]
    • temperature: 0.2 [0과 2 사이의 값, 높을수록 무작위적이며 창의적인 대답을 내놓음]
    • messages(*):
      • role: user
      • content:
        • type: text
        • text: Introduction과 Table data

Table

LLM이 table을 인식하기 위해서는 table이 적절한 형식으로 변환되어야 합니다. LLM이 인식 할 수 있는 형식은 크게 두가지입니다.

Markdown

마크다운은 일반 텍스트 기반의 경량 마크업 언어입니다. 누구나 쉽게 작성할 수 있다는 장점이 있으며 표 또한 텍스트를 통해 생성 할 수 있습니다. 다만 복잡한 형태의 표 (예: 계층 구조)의 경우 표현하기 어렵다는 단점이 있습니다.

convert_to_markdown <- function(table) {
  df_from_ft <- as.data.frame(table)
  markdown_table <- capture.output(cat(knitr::kable(table, format = "markdown"), sep = "\n"))
  return(paste(markdown_table, collapse = "\n"))
}
head(mtcars[1:5])
                   mpg cyl disp  hp drat
Mazda RX4         21.0   6  160 110 3.90
Mazda RX4 Wag     21.0   6  160 110 3.90
Datsun 710        22.8   4  108  93 3.85
Hornet 4 Drive    21.4   6  258 110 3.08
Hornet Sportabout 18.7   8  360 175 3.15
Valiant           18.1   6  225 105 2.76
cat(convert_to_markdown(head(mtcars[1:5])))
|                  |  mpg| cyl| disp|  hp| drat|
|:-----------------|----:|---:|----:|---:|----:|
|Mazda RX4         | 21.0|   6|  160| 110| 3.90|
|Mazda RX4 Wag     | 21.0|   6|  160| 110| 3.90|
|Datsun 710        | 22.8|   4|  108|  93| 3.85|
|Hornet 4 Drive    | 21.4|   6|  258| 110| 3.08|
|Hornet Sportabout | 18.7|   8|  360| 175| 3.15|
|Valiant           | 18.1|   6|  225| 105| 2.76|

HTML

일반적인 data.frame 객체는 마크다운으로 변환할 수 있습니다.

그러나 flextable처럼 Cell이 합쳐진 형태나 계층적 구조일 경우 markdown으로의 변환이 어렵습니다.

이 경우 html table 형태로 변환할 수 있습니다.

proc_freq(mtcars, "gear", "vs")

gear

vs

0

1

Total

3

Count

12 (37.5%)

3 (9.4%)

15 (46.9%)

Mar. pct (1)

66.7% ; 80.0%

21.4% ; 20.0%

4

Count

2 (6.2%)

10 (31.2%)

12 (37.5%)

Mar. pct

11.1% ; 16.7%

71.4% ; 83.3%

5

Count

4 (12.5%)

1 (3.1%)

5 (15.6%)

Mar. pct

22.2% ; 80.0%

7.1% ; 20.0%

Total

Count

18 (56.2%)

14 (43.8%)

32 (100.0%)

(1) Columns and rows percentages

cat(proc_freq(mtcars, "gear", "vs") %>% to_html)
<div class="tabwid"><style>.cl-d42a3a38{}.cl-d4230326{font-family:'DejaVu Sans';font-size:11pt;font-weight:normal;font-style:normal;text-decoration:none;color:rgba(0, 0, 0, 1.00);background-color:transparent;}.cl-d4230330{font-family:'DejaVu Sans';font-size:6.6pt;font-weight:normal;font-style:normal;text-decoration:none;color:rgba(0, 0, 0, 1.00);background-color:transparent;position: relative;bottom:3.3pt;}.cl-d425c64c{margin:0;text-align:left;border-bottom: 0 solid rgba(0, 0, 0, 1.00);border-top: 0 solid rgba(0, 0, 0, 1.00);border-left: 0 solid rgba(0, 0, 0, 1.00);border-right: 0 solid rgba(0, 0, 0, 1.00);padding-bottom:5pt;padding-top:5pt;padding-left:5pt;padding-right:5pt;line-height: 1;background-color:transparent;}.cl-d425c660{margin:0;text-align:center;border-bottom: 0 solid rgba(0, 0, 0, 1.00);border-top: 0 solid rgba(0, 0, 0, 1.00);border-left: 0 solid rgba(0, 0, 0, 1.00);border-right: 0 solid rgba(0, 0, 0, 1.00);padding-bottom:5pt;padding-top:5pt;padding-left:5pt;padding-right:5pt;line-height: 1;background-color:transparent;}.cl-d425c661{margin:0;text-align:left;border-bottom: 0 solid rgba(0, 0, 0, 1.00);border-top: 0 solid rgba(0, 0, 0, 1.00);border-left: 0 solid rgba(0, 0, 0, 1.00);border-right: 0 solid rgba(0, 0, 0, 1.00);padding-bottom:5pt;padding-top:5pt;padding-left:5pt;padding-right:5pt;line-height: 1;background-color:transparent;}.cl-d425c662{margin:0;text-align:center;border-bottom: 0 solid rgba(0, 0, 0, 1.00);border-top: 0 solid rgba(0, 0, 0, 1.00);border-left: 0 solid rgba(0, 0, 0, 1.00);border-right: 0 solid rgba(0, 0, 0, 1.00);padding-bottom:5pt;padding-top:5pt;padding-left:5pt;padding-right:5pt;line-height: 1;background-color:transparent;}.cl-d425c66a{margin:0;text-align:left;border-bottom: 0 solid rgba(0, 0, 0, 1.00);border-top: 0 solid rgba(0, 0, 0, 1.00);border-left: 0 solid rgba(0, 0, 0, 1.00);border-right: 0 solid rgba(0, 0, 0, 1.00);padding-bottom:5pt;padding-top:5pt;padding-left:5pt;padding-right:5pt;line-height: 1;background-color:transparent;}.cl-d425e550{width:0.625in;background-color:transparent;vertical-align: bottom;border-bottom: 1.5pt solid rgba(102, 102, 102, 1.00);border-top: 1.5pt solid rgba(102, 102, 102, 1.00);border-left: 0 solid rgba(0, 0, 0, 1.00);border-right: 0 solid rgba(0, 0, 0, 1.00);margin-bottom:0;margin-top:0;margin-left:0;margin-right:0;}.cl-d425e551{width:0.952in;background-color:transparent;vertical-align: bottom;border-bottom: 1.5pt solid rgba(102, 102, 102, 1.00);border-top: 1.5pt solid rgba(102, 102, 102, 1.00);border-left: 0 solid rgba(0, 0, 0, 1.00);border-right: 1pt solid rgba(102, 102, 102, 1.00);margin-bottom:0;margin-top:0;margin-left:0;margin-right:0;}.cl-d425e55a{width:1.279in;background-color:transparent;vertical-align: middle;border-bottom: 0.75pt solid rgba(102, 102, 102, 1.00);border-top: 1.5pt solid rgba(102, 102, 102, 1.00);border-left: 1pt solid rgba(102, 102, 102, 1.00);border-right: 0 solid rgba(0, 0, 0, 1.00);margin-bottom:0;margin-top:0;margin-left:0;margin-right:0;}.cl-d425e55b{width:1.279in;background-color:transparent;vertical-align: middle;border-bottom: 0.75pt solid rgba(102, 102, 102, 1.00);border-top: 1.5pt solid rgba(102, 102, 102, 1.00);border-left: 0 solid rgba(0, 0, 0, 1.00);border-right: 1pt solid rgba(102, 102, 102, 1.00);margin-bottom:0;margin-top:0;margin-left:0;margin-right:0;}.cl-d425e564{width:1.117in;background-color:transparent;vertical-align: middle;border-bottom: 0.75pt solid rgba(102, 102, 102, 1.00);border-top: 1.5pt solid rgba(102, 102, 102, 1.00);border-left: 1pt solid rgba(102, 102, 102, 1.00);border-right: 0 solid rgba(0, 0, 0, 1.00);margin-bottom:0;margin-top:0;margin-left:0;margin-right:0;}.cl-d425e565{width:0.625in;background-color:transparent;vertical-align: bottom;border-bottom: 1.5pt solid rgba(102, 102, 102, 1.00);border-top: 0 solid rgba(0, 0, 0, 1.00);border-left: 0 solid rgba(0, 0, 0, 1.00);border-right: 0 solid rgba(0, 0, 0, 1.00);margin-bottom:0;margin-top:0;margin-left:0;margin-right:0;}.cl-d425e566{width:0.952in;background-color:transparent;vertical-align: bottom;border-bottom: 1.5pt solid rgba(102, 102, 102, 1.00);border-top: 0 solid rgba(0, 0, 0, 1.00);border-left: 0 solid rgba(0, 0, 0, 1.00);border-right: 1pt solid rgba(102, 102, 102, 1.00);margin-bottom:0;margin-top:0;margin-left:0;margin-right:0;}.cl-d425e567{width:1.279in;background-color:transparent;vertical-align: middle;border-bottom: 1.5pt solid rgba(102, 102, 102, 1.00);border-top: 0.75pt solid rgba(102, 102, 102, 1.00);border-left: 1pt solid rgba(102, 102, 102, 1.00);border-right: 0 solid rgba(0, 0, 0, 1.00);margin-bottom:0;margin-top:0;margin-left:0;margin-right:0;}.cl-d425e56e{width:1.279in;background-color:transparent;vertical-align: middle;border-bottom: 1.5pt solid rgba(102, 102, 102, 1.00);border-top: 0.75pt solid rgba(102, 102, 102, 1.00);border-left: 0 solid rgba(0, 0, 0, 1.00);border-right: 1pt solid rgba(102, 102, 102, 1.00);margin-bottom:0;margin-top:0;margin-left:0;margin-right:0;}.cl-d425e56f{width:1.117in;background-color:transparent;vertical-align: middle;border-bottom: 1.5pt solid rgba(102, 102, 102, 1.00);border-top: 0.75pt solid rgba(102, 102, 102, 1.00);border-left: 1pt solid rgba(102, 102, 102, 1.00);border-right: 0 solid rgba(0, 0, 0, 1.00);margin-bottom:0;margin-top:0;margin-left:0;margin-right:0;}.cl-d425e570{width:0.625in;background-color:transparent;vertical-align: top;border-bottom: 0 solid rgba(0, 0, 0, 1.00);border-top: 0 solid rgba(0, 0, 0, 1.00);border-left: 0 solid rgba(0, 0, 0, 1.00);border-right: 0 solid rgba(0, 0, 0, 1.00);margin-bottom:0;margin-top:0;margin-left:0;margin-right:0;}.cl-d425e571{width:0.952in;background-color:transparent;vertical-align: top;border-bottom: 0 solid rgba(0, 0, 0, 1.00);border-top: 0 solid rgba(0, 0, 0, 1.00);border-left: 0 solid rgba(0, 0, 0, 1.00);border-right: 1pt solid rgba(102, 102, 102, 1.00);margin-bottom:0;margin-top:0;margin-left:0;margin-right:0;}.cl-d425e572{width:1.279in;background-color:transparent;vertical-align: top;border-bottom: 0 solid rgba(0, 0, 0, 1.00);border-top: 0 solid rgba(0, 0, 0, 1.00);border-left: 1pt solid rgba(102, 102, 102, 1.00);border-right: 0 solid rgba(0, 0, 0, 1.00);margin-bottom:0;margin-top:0;margin-left:0;margin-right:0;}.cl-d425e578{width:1.279in;background-color:transparent;vertical-align: top;border-bottom: 0 solid rgba(0, 0, 0, 1.00);border-top: 0 solid rgba(0, 0, 0, 1.00);border-left: 0 solid rgba(0, 0, 0, 1.00);border-right: 1pt solid rgba(102, 102, 102, 1.00);margin-bottom:0;margin-top:0;margin-left:0;margin-right:0;}.cl-d425e579{width:1.117in;background-color:transparent;vertical-align: top;border-bottom: 0 solid rgba(0, 0, 0, 1.00);border-top: 0 solid rgba(0, 0, 0, 1.00);border-left: 1pt solid rgba(102, 102, 102, 1.00);border-right: 0 solid rgba(0, 0, 0, 1.00);margin-bottom:0;margin-top:0;margin-left:0;margin-right:0;}.cl-d425e57a{width:0.625in;background-color:transparent;vertical-align: top;border-bottom: 0 solid rgba(0, 0, 0, 1.00);border-top: 0 solid rgba(0, 0, 0, 1.00);border-left: 0 solid rgba(0, 0, 0, 1.00);border-right: 0 solid rgba(0, 0, 0, 1.00);margin-bottom:0;margin-top:0;margin-left:0;margin-right:0;}.cl-d425e57b{width:0.952in;background-color:transparent;vertical-align: top;border-bottom: 0 solid rgba(0, 0, 0, 1.00);border-top: 0 solid rgba(0, 0, 0, 1.00);border-left: 0 solid rgba(0, 0, 0, 1.00);border-right: 1pt solid rgba(102, 102, 102, 1.00);margin-bottom:0;margin-top:0;margin-left:0;margin-right:0;}.cl-d425e582{width:1.279in;background-color:transparent;vertical-align: top;border-bottom: 0 solid rgba(0, 0, 0, 1.00);border-top: 0 solid rgba(0, 0, 0, 1.00);border-left: 1pt solid rgba(102, 102, 102, 1.00);border-right: 0 solid rgba(0, 0, 0, 1.00);margin-bottom:0;margin-top:0;margin-left:0;margin-right:0;}.cl-d425e583{width:1.279in;background-color:transparent;vertical-align: top;border-bottom: 0 solid rgba(0, 0, 0, 1.00);border-top: 0 solid rgba(0, 0, 0, 1.00);border-left: 0 solid rgba(0, 0, 0, 1.00);border-right: 1pt solid rgba(102, 102, 102, 1.00);margin-bottom:0;margin-top:0;margin-left:0;margin-right:0;}.cl-d425e584{width:1.117in;background-color:transparent;vertical-align: top;border-bottom: 0 solid rgba(0, 0, 0, 1.00);border-top: 0 solid rgba(0, 0, 0, 1.00);border-left: 1pt solid rgba(102, 102, 102, 1.00);border-right: 0 solid rgba(0, 0, 0, 1.00);margin-bottom:0;margin-top:0;margin-left:0;margin-right:0;}.cl-d425e585{width:0.625in;background-color:transparent;vertical-align: top;border-bottom: 1pt solid rgba(102, 102, 102, 1.00);border-top: 0 solid rgba(0, 0, 0, 1.00);border-left: 0 solid rgba(0, 0, 0, 1.00);border-right: 0 solid rgba(0, 0, 0, 1.00);margin-bottom:0;margin-top:0;margin-left:0;margin-right:0;}.cl-d425e58c{width:0.952in;background-color:transparent;vertical-align: top;border-bottom: 1pt solid rgba(102, 102, 102, 1.00);border-top: 0 solid rgba(0, 0, 0, 1.00);border-left: 0 solid rgba(0, 0, 0, 1.00);border-right: 1pt solid rgba(102, 102, 102, 1.00);margin-bottom:0;margin-top:0;margin-left:0;margin-right:0;}.cl-d425e58d{width:1.279in;background-color:transparent;vertical-align: top;border-bottom: 1pt solid rgba(102, 102, 102, 1.00);border-top: 0 solid rgba(0, 0, 0, 1.00);border-left: 1pt solid rgba(102, 102, 102, 1.00);border-right: 0 solid rgba(0, 0, 0, 1.00);margin-bottom:0;margin-top:0;margin-left:0;margin-right:0;}.cl-d425e58e{width:1.279in;background-color:transparent;vertical-align: top;border-bottom: 1pt solid rgba(102, 102, 102, 1.00);border-top: 0 solid rgba(0, 0, 0, 1.00);border-left: 0 solid rgba(0, 0, 0, 1.00);border-right: 1pt solid rgba(102, 102, 102, 1.00);margin-bottom:0;margin-top:0;margin-left:0;margin-right:0;}.cl-d425e58f{width:1.117in;background-color:transparent;vertical-align: top;border-bottom: 1pt solid rgba(102, 102, 102, 1.00);border-top: 0 solid rgba(0, 0, 0, 1.00);border-left: 1pt solid rgba(102, 102, 102, 1.00);border-right: 0 solid rgba(0, 0, 0, 1.00);margin-bottom:0;margin-top:0;margin-left:0;margin-right:0;}.cl-d425e596{width:0.625in;background-color:transparent;vertical-align: top;border-bottom: 1.5pt solid rgba(102, 102, 102, 1.00);border-top: 1pt solid rgba(102, 102, 102, 1.00);border-left: 0 solid rgba(0, 0, 0, 1.00);border-right: 0 solid rgba(0, 0, 0, 1.00);margin-bottom:0;margin-top:0;margin-left:0;margin-right:0;}.cl-d425e597{width:0.952in;background-color:transparent;vertical-align: top;border-bottom: 1.5pt solid rgba(102, 102, 102, 1.00);border-top: 1pt solid rgba(102, 102, 102, 1.00);border-left: 0 solid rgba(0, 0, 0, 1.00);border-right: 1pt solid rgba(102, 102, 102, 1.00);margin-bottom:0;margin-top:0;margin-left:0;margin-right:0;}.cl-d425e598{width:1.279in;background-color:transparent;vertical-align: top;border-bottom: 1.5pt solid rgba(102, 102, 102, 1.00);border-top: 1pt solid rgba(102, 102, 102, 1.00);border-left: 1pt solid rgba(102, 102, 102, 1.00);border-right: 0 solid rgba(0, 0, 0, 1.00);margin-bottom:0;margin-top:0;margin-left:0;margin-right:0;}.cl-d425e599{width:1.279in;background-color:transparent;vertical-align: top;border-bottom: 1.5pt solid rgba(102, 102, 102, 1.00);border-top: 1pt solid rgba(102, 102, 102, 1.00);border-left: 0 solid rgba(0, 0, 0, 1.00);border-right: 1pt solid rgba(102, 102, 102, 1.00);margin-bottom:0;margin-top:0;margin-left:0;margin-right:0;}.cl-d425e59a{width:1.117in;background-color:transparent;vertical-align: top;border-bottom: 1.5pt solid rgba(102, 102, 102, 1.00);border-top: 1pt solid rgba(102, 102, 102, 1.00);border-left: 1pt solid rgba(102, 102, 102, 1.00);border-right: 0 solid rgba(0, 0, 0, 1.00);margin-bottom:0;margin-top:0;margin-left:0;margin-right:0;}.cl-d425e59b{width:0.625in;background-color:transparent;vertical-align: middle;border-bottom: 0 solid rgba(255, 255, 255, 0.00);border-top: 0 solid rgba(255, 255, 255, 0.00);border-left: 0 solid rgba(255, 255, 255, 0.00);border-right: 0 solid rgba(255, 255, 255, 0.00);margin-bottom:0;margin-top:0;margin-left:0;margin-right:0;}.cl-d425e5a0{width:0.952in;background-color:transparent;vertical-align: middle;border-bottom: 0 solid rgba(255, 255, 255, 0.00);border-top: 0 solid rgba(255, 255, 255, 0.00);border-left: 0 solid rgba(255, 255, 255, 0.00);border-right: 1pt solid rgba(102, 102, 102, 1.00);margin-bottom:0;margin-top:0;margin-left:0;margin-right:0;}.cl-d425e5a1{width:1.279in;background-color:transparent;vertical-align: middle;border-bottom: 0 solid rgba(255, 255, 255, 0.00);border-top: 0 solid rgba(255, 255, 255, 0.00);border-left: 1pt solid rgba(102, 102, 102, 1.00);border-right: 0 solid rgba(255, 255, 255, 0.00);margin-bottom:0;margin-top:0;margin-left:0;margin-right:0;}.cl-d425e5a2{width:1.279in;background-color:transparent;vertical-align: middle;border-bottom: 0 solid rgba(255, 255, 255, 0.00);border-top: 0 solid rgba(255, 255, 255, 0.00);border-left: 0 solid rgba(255, 255, 255, 0.00);border-right: 1pt solid rgba(102, 102, 102, 1.00);margin-bottom:0;margin-top:0;margin-left:0;margin-right:0;}.cl-d425e5aa{width:1.117in;background-color:transparent;vertical-align: middle;border-bottom: 0 solid rgba(255, 255, 255, 0.00);border-top: 0 solid rgba(255, 255, 255, 0.00);border-left: 1pt solid rgba(102, 102, 102, 1.00);border-right: 0 solid rgba(255, 255, 255, 0.00);margin-bottom:0;margin-top:0;margin-left:0;margin-right:0;}.tabwid {
  font-size: initial;
  padding-bottom: 1em;
}

.tabwid table{
  border-spacing:0px !important;
  border-collapse:collapse;
  line-height:1;
  margin-left:auto;
  margin-right:auto;
  border-width: 0;
  border-color: transparent;
  caption-side: top;
}
.tabwid-caption-bottom table{
  caption-side: bottom;
}
.tabwid_left table{
  margin-left:0;
}
.tabwid_right table{
  margin-right:0;
}
.tabwid td, .tabwid th {
    padding: 0;
}
.tabwid a {
  text-decoration: none;
}
.tabwid thead {
    background-color: transparent;
}
.tabwid tfoot {
    background-color: transparent;
}
.tabwid table tr {
background-color: transparent;
}
.katex-display {
    margin: 0 0 !important;
}</style><table data-quarto-disable-processing='true' class='cl-d42a3a38'><thead><tr style="overflow-wrap:break-word;"><th  rowspan="2"class="cl-d425e550"><p class="cl-d425c64c"><span class="cl-d4230326">gear</span></p></th><th  rowspan="2"class="cl-d425e551"><p class="cl-d425c64c"><span class="cl-d4230326"></span></p></th><th  colspan="3"class="cl-d425e55a"><p class="cl-d425c660"><span class="cl-d4230326">vs</span></p></th></tr><tr style="overflow-wrap:break-word;"><th class="cl-d425e567"><p class="cl-d425c660"><span class="cl-d4230326">0</span></p></th><th class="cl-d425e56e"><p class="cl-d425c660"><span class="cl-d4230326">1</span></p></th><th class="cl-d425e56f"><p class="cl-d425c660"><span class="cl-d4230326">Total</span></p></th></tr></thead><tbody><tr style="overflow-wrap:break-word;"><td  rowspan="2"class="cl-d425e570"><p class="cl-d425c661"><span class="cl-d4230326">3</span></p></td><td class="cl-d425e571"><p class="cl-d425c661"><span class="cl-d4230326">Count</span></p></td><td class="cl-d425e572"><p class="cl-d425c662"><span class="cl-d4230326">12 (37.5%)</span><span class="cl-d4230326"></span></p></td><td class="cl-d425e578"><p class="cl-d425c662"><span class="cl-d4230326">3 (9.4%)</span><span class="cl-d4230326"></span></p></td><td class="cl-d425e579"><p class="cl-d425c662"><span class="cl-d4230326">15 (46.9%)</span><span class="cl-d4230326"></span></p></td></tr><tr style="overflow-wrap:break-word;"><td class="cl-d425e57b"><p class="cl-d425c661"><span class="cl-d4230326">Mar. pct</span><span class="cl-d4230330"> (1)</span></p></td><td class="cl-d425e582"><p class="cl-d425c662"><span class="cl-d4230326"></span><span class="cl-d4230326">66.7% ; 80.0%</span></p></td><td class="cl-d425e583"><p class="cl-d425c662"><span class="cl-d4230326"></span><span class="cl-d4230326">21.4% ; 20.0%</span></p></td><td class="cl-d425e584"><p class="cl-d425c662"><span class="cl-d4230326"></span><span class="cl-d4230326"></span></p></td></tr><tr style="overflow-wrap:break-word;"><td  rowspan="2"class="cl-d425e570"><p class="cl-d425c661"><span class="cl-d4230326">4</span></p></td><td class="cl-d425e571"><p class="cl-d425c661"><span class="cl-d4230326">Count</span></p></td><td class="cl-d425e572"><p class="cl-d425c662"><span class="cl-d4230326">2 (6.2%)</span><span class="cl-d4230326"></span></p></td><td class="cl-d425e578"><p class="cl-d425c662"><span class="cl-d4230326">10 (31.2%)</span><span class="cl-d4230326"></span></p></td><td class="cl-d425e579"><p class="cl-d425c662"><span class="cl-d4230326">12 (37.5%)</span><span class="cl-d4230326"></span></p></td></tr><tr style="overflow-wrap:break-word;"><td class="cl-d425e57b"><p class="cl-d425c661"><span class="cl-d4230326">Mar. pct</span></p></td><td class="cl-d425e582"><p class="cl-d425c662"><span class="cl-d4230326"></span><span class="cl-d4230326">11.1% ; 16.7%</span></p></td><td class="cl-d425e583"><p class="cl-d425c662"><span class="cl-d4230326"></span><span class="cl-d4230326">71.4% ; 83.3%</span></p></td><td class="cl-d425e584"><p class="cl-d425c662"><span class="cl-d4230326"></span><span class="cl-d4230326"></span></p></td></tr><tr style="overflow-wrap:break-word;"><td  rowspan="2"class="cl-d425e570"><p class="cl-d425c661"><span class="cl-d4230326">5</span></p></td><td class="cl-d425e571"><p class="cl-d425c661"><span class="cl-d4230326">Count</span></p></td><td class="cl-d425e572"><p class="cl-d425c662"><span class="cl-d4230326">4 (12.5%)</span><span class="cl-d4230326"></span></p></td><td class="cl-d425e578"><p class="cl-d425c662"><span class="cl-d4230326">1 (3.1%)</span><span class="cl-d4230326"></span></p></td><td class="cl-d425e579"><p class="cl-d425c662"><span class="cl-d4230326">5 (15.6%)</span><span class="cl-d4230326"></span></p></td></tr><tr style="overflow-wrap:break-word;"><td class="cl-d425e58c"><p class="cl-d425c661"><span class="cl-d4230326">Mar. pct</span></p></td><td class="cl-d425e58d"><p class="cl-d425c662"><span class="cl-d4230326"></span><span class="cl-d4230326">22.2% ; 80.0%</span></p></td><td class="cl-d425e58e"><p class="cl-d425c662"><span class="cl-d4230326"></span><span class="cl-d4230326">7.1% ; 20.0%</span></p></td><td class="cl-d425e58f"><p class="cl-d425c662"><span class="cl-d4230326"></span><span class="cl-d4230326"></span></p></td></tr><tr style="overflow-wrap:break-word;"><td class="cl-d425e596"><p class="cl-d425c661"><span class="cl-d4230326">Total</span></p></td><td class="cl-d425e597"><p class="cl-d425c661"><span class="cl-d4230326">Count</span></p></td><td class="cl-d425e598"><p class="cl-d425c662"><span class="cl-d4230326">18 (56.2%)</span><span class="cl-d4230326"></span></p></td><td class="cl-d425e599"><p class="cl-d425c662"><span class="cl-d4230326">14 (43.8%)</span><span class="cl-d4230326"></span></p></td><td class="cl-d425e59a"><p class="cl-d425c662"><span class="cl-d4230326">32 (100.0%)</span><span class="cl-d4230326"></span></p></td></tr></tbody><tfoot><tr style="overflow-wrap:break-word;"><td  colspan="5"class="cl-d425e59b"><p class="cl-d425c66a"><span class="cl-d4230330"> (1)</span><span class="cl-d4230326"> Columns and rows percentages</span></p></td></tr></tfoot></table></div>

Figure

만약 이미지에 대한 설명을 원한다면 이미지에 대한 파라미터를 추가해야합니다.

  • Header
    • Content-Type: application/json
    • Authorization: Bearer $OPENAI_API_KEY
  • Body
    • model(*): gpt-4o-mini [openAI의 다른 모델도 사용할 수 있습니다]
    • temperature: 0.2 [0과 2 사이의 값, 높을수록 무작위적이며 창의적인 대답을 내놓음]
    • messages(*):
      • role: user
      • content:
        • type: text
        • text: Introduction과 Table data
        • type: image_url
        • image_url:
          • url: base64로 인코딩된 이미지

이미지는 base64로 인코딩되어 전달됩니다.

LLM_description <- function(inst = "다음에 대해 설명하시오", ## 지침
                         add_inst = NULL, ## 추가 지침
                         table = "", ## 데이터가 포함된 표
                         image_path = NULL, ## 이미지 경로 (Local 또는 웹)
                         model = "gpt-4o-mini", ## 사용 할 LLM 모델
                         temperature = 0.2, ## 무작위성 (낮을수록 일관된 답변)
                         endpoint = "https://api.openai.com/v1/" ## 엔드포인트
                         ){
  
  ## LLM에게 전달 할 내용
  ### 주요 지침
  ### 추가 지침
  ### 표
  text <- paste(
    "주요 지침: ",
    inst,
    ifelse(is.null(add_inst), ## 추가 지침이 존재 할 경우 추가
           " ",
           "추가 지침: "),
    add_inst,
    "도표: ",
    table, ## Markdown 또는 html 형태의 table
    sep = "\n\n"

  )
  
  
  url <- NULL
  if(!is.null(image_path)){ ## image path가 전달
    if(file.exists(image_path)){
      ## 로컬파일인 경우
      url <- paste0("data:image/png;base64,",base64encode(image_path))
    }else{
      ## 로컬파일이 아닌 경우(Web 이미지 가정)
      
      ## 이미지 다운로드
      resp <- request(image_path) %>% 
        req_perform()
      
      
      if(resp$status_code == 200){
        ## 정상적인 응답 시
        image_raw <- resp_body_raw(resp)
        encoded_image <- base64encode(image_raw)
        url <- paste0("data:image/png;base64,",encoded_image )
        
      } else{
        ## 비정상적인 응답 시
        warning("Failed to download the image. Status code:", response$status_code)
      }
      
      
    }
  }
  
  ## 이미지 컴포넌트
  image_cmp <- NULL
  
  
  if(!is.null(url)){
    ## 이미지가 존재할 경우
    
    ## 이미지
    image_cmp <- list(
            type = "image_url",
            image_url = list(
              url = url
            )
          )
    
    ## 이미지가 포함된 content
    content <- 
      list(
          list(
            type = "text",
            text =  text
          ),
          image_cmp

        )
  }else{
    ## 이미지가 존재하지 않을 경우
    ## text만 content에 포함
    content <- list(
          list(
            type = "text",
            text =  text
          )

        )
  }
  
  

req <- request(endpoint) %>% ## 엔드포인트
  req_url_path_append("chat") %>% ## 주소 1
  req_url_path_append("completions") %>% ##주소 2
  req_headers(
    `Content-Type` = "application/json",
    Authorization = paste("Bearer", openai_key) ## API키 전달
  ) %>% 
  req_body_json(
    list(
      model = model, ## LLM 모델
      temperature = temperature, 
      messages = list(list(
        role = "user",
        content = content
      )
    )),
    auto_unbox = T
  )



resp <- req %>% 
  req_perform() ## 요청 실행
tmp <- resp %>% 
  resp_body_json() ## 응답을 json 형태로 전환



return(tmp$choices[[1]]$message$content) ## 응답 부분만 반환
}

Database (Optional)

Quarto 문서를 랜더링 할 때마다 OpenAI endpoint에 결과를 요청할 경우 상당한 비용을 초래할 수 있습니다. 따라서 동일한 parameter로 request 할 경우 DB에 저장된 결과를 대신 반환하도록 sqlite3를 이용하여 캐시 DB를 구성했습니다. 본 포스팅에서는 생략합니다.

Examples

data.frame 형태의 표에 대한 설명

head(mtcars)
                   mpg cyl disp  hp drat    wt  qsec vs am gear carb
Mazda RX4         21.0   6  160 110 3.90 2.620 16.46  0  1    4    4
Mazda RX4 Wag     21.0   6  160 110 3.90 2.875 17.02  0  1    4    4
Datsun 710        22.8   4  108  93 3.85 2.320 18.61  1  1    4    1
Hornet 4 Drive    21.4   6  258 110 3.08 3.215 19.44  1  0    3    1
Hornet Sportabout 18.7   8  360 175 3.15 3.440 17.02  0  0    3    2
Valiant           18.1   6  225 105 2.76 3.460 20.22  1  0    3    1
cat(LLM_description(
  inst = "다음 표에 대해 설명하시오",
  add_inst = "이 표는 자동차에 관한 표이다.",
  table = convert_to_markdown(mtcars)
))
이 표는 다양한 자동차 모델에 대한 여러 가지 성능 및 사양 데이터를 나열하고 있습니다. 각 열은 자동차의 특정 특성을 나타내며, 각 행은 특정 모델에 대한 정보를 제공합니다. 아래는 각 열의 설명입니다:

1. **mpg (Miles Per Gallon)**: 연비를 나타내며, 차량이 1갤런의 연료로 주행할 수 있는 마일 수를 의미합니다. 이 값이 높을수록 연료 효율성이 좋습니다.

2. **cyl (Cylinders)**: 엔진의 실린더 수를 나타냅니다. 일반적으로 실린더 수가 많을수록 엔진의 출력이 높아지지만, 연비는 낮아질 수 있습니다.

3. **disp (Displacement)**: 엔진의 배기량을 나타내며, 보통 리터 또는 세제곱 인치로 측정됩니다. 배기량이 클수록 엔진의 힘이 강해지는 경향이 있습니다.

4. **hp (Horsepower)**: 엔진의 출력, 즉 마력을 나타냅니다. 마력이 높을수록 차량의 성능이 좋습니다.

5. **drat (Rear Axle Ratio)**: 후륜 구동차의 후축 비율을 나타내며, 엔진의 회전 속도와 바퀴의 회전 속도 간의 비율을 의미합니다. 이 값이 높을수록 가속력이 좋지만 연비는 떨어질 수 있습니다.

6. **wt (Weight)**: 차량의 무게를 나타내며, 일반적으로 파운드로 측정됩니다. 무게가 가벼울수록 연비가 좋고, 주행 성능이 향상될 수 있습니다.

7. **qsec (Quarter Mile Time)**: 1/4 마일(약 400미터) 주행에 소요되는 시간을 나타냅니다. 이 값이 낮을수록 차량의 가속력이 좋습니다.

8. **vs (V/S)**: 엔진의 배치 방식으로, 0은 V형 엔진, 1은 직렬 엔진을 의미합니다.

9. **am (Transmission Type)**: 변속기의 종류를 나타내며, 0은 자동 변속기, 1은 수동 변속기를 의미합니다.

10. **gear (Number of Gears)**: 변속기의 기어 수를 나타냅니다. 기어 수가 많을수록 다양한 주행 조건에 적합할 수 있습니다.

11. **carb (Carburetors)**: 카뷰레터의 수를 나타내며, 연료 혼합 비율을 조절하는 장치입니다. 카뷰레터 수가 많을수록 연료 공급이 원활해질 수 있습니다.

이 표를 통해 다양한 자동차 모델의 성능과 사양을 비교할 수 있으며, 소비자들이 자신의 필요에 맞는 차량을 선택하는 데 유용한 정보를 제공합니다. 예를 들어, 연비가 중요한 소비자는 mpg가 높은 차량을 선호할 것이고, 성능이 중요한 소비자는 hp와 qsec 값이 높은 차량을 선택할 수 있습니다.

flextable 형태의 표에 대한 설명

proc_freq(mtcars, "gear", "vs")

gear

vs

0

1

Total

3

Count

12 (37.5%)

3 (9.4%)

15 (46.9%)

Mar. pct (1)

66.7% ; 80.0%

21.4% ; 20.0%

4

Count

2 (6.2%)

10 (31.2%)

12 (37.5%)

Mar. pct

11.1% ; 16.7%

71.4% ; 83.3%

5

Count

4 (12.5%)

1 (3.1%)

5 (15.6%)

Mar. pct

22.2% ; 80.0%

7.1% ; 20.0%

Total

Count

18 (56.2%)

14 (43.8%)

32 (100.0%)

(1) Columns and rows percentages

cat(LLM_description(
  inst = "다음 표에 대해 설명하시오.",
  add_inst = "그래프는 gear 와 vs를 변수로 갖는 frequency table 이다.",
  table = proc_freq(mtcars, "gear", "vs") %>% to_html
  ))
제공된 표는 "gear"와 "vs"라는 두 변수 간의 빈도 수를 나타내는 빈도표입니다. 이 표는 각 gear 값에 대해 vs 값이 0 또는 1인 경우의 수와 비율을 보여줍니다. 

### 표의 구성 요소 설명:

1. **열 제목**:
   - 첫 번째 열은 "gear" 값을 나타내며, 3, 4, 5의 세 가지 값이 있습니다.
   - 두 번째 열은 비어 있으며, 주로 "Count"와 "Mar. pct"를 구분하는 역할을 합니다.
   - 세 번째 열부터 다섯 번째 열은 "vs" 값에 대한 정보를 제공합니다. 세 번째 열은 vs가 0인 경우의 수, 네 번째 열은 vs가 1인 경우의 수, 다섯 번째 열은 두 경우의 합계입니다.

2. **행 제목**:
   - 각 gear 값에 대해 두 개의 행이 있습니다. 첫 번째 행은 각 경우의 수와 비율을 나타내고, 두 번째 행은 각 경우의 비율을 나타냅니다.

3. **데이터**:
   - 예를 들어, gear가 3일 때, vs가 0인 경우는 12건(37.5%), vs가 1인 경우는 3건(9.4%)이며, 총 15건(46.9%)입니다.
   - gear가 4일 때, vs가 0인 경우는 2건(6.2%), vs가 1인 경우는 10건(31.2%)이며, 총 12건(37.5%)입니다.
   - gear가 5일 때, vs가 0인 경우는 4건(12.5%), vs가 1인 경우는 1건(3.1%)이며, 총 5건(15.6%)입니다.

4. **총계**:
   - 마지막 행은 각 gear 값에 대한 총계를 보여줍니다. 예를 들어, vs가 0인 경우 총 18건(56.2%), vs가 1인 경우 총 14건(43.8%)이며, 전체 총계는 32건(100%)입니다.

5. **비율**:
   - "Mar. pct"는 각 gear 값에 대한 vs의 비율을 나타내며, 각 gear에 대해 두 개의 비율이 제공됩니다.

### 요약:
이 빈도표는 gear와 vs 간의 관계를 명확하게 보여주며, 각 gear 값에 따른 vs의 발생 빈도와 비율을 분석하는 데 유용합니다. 이를 통해 특정 gear 값이 vs에 미치는 영향을 이해할 수 있습니다.

그래프 등의 이미지에 대한 설명

plot_am_bar <- ggplot(mtcars) +
  geom_bar(aes(factor(am)))

plot_am_bar

ggsave("img/am.png")
Saving 7 x 5 in image
plot_am_bar_build <- ggplot_build(plot_am_bar)
cat(LLM_description(
  inst = "다음 그래프에 대해 설명하시오.",
  add_inst = "그래프는 자동차의 변속기 유형과 관련되어있다.",
  table = convert_to_markdown( plot_am_bar_build$data),
  image_path = "img/am.png"
  ))
그래프는 자동차의 변속기 유형에 따른 분포를 나타내고 있습니다. x축은 변속기 유형을 나타내며, 0은 자동 변속기(automatic), 1은 수동 변속기(manual)를 의미합니다. y축은 각 변속기 유형에 해당하는 자동차의 수(count)를 나타냅니다.

그래프를 보면, 자동 변속기를 가진 자동차의 수가 약 19대인 반면, 수동 변속기를 가진 자동차는 약 13대입니다. 이는 자동 변속기를 가진 자동차가 수동 변속기보다 더 많다는 것을 보여줍니다. 

전체적으로, 이 그래프는 자동차의 변속기 유형에 대한 분포를 시각적으로 표현하며, 자동 변속기가 더 일반적이라는 점을 강조하고 있습니다.

Reuse

Citation

BibTeX citation:
@online{sun_park2024,
  author = {Sun Park, Young},
  title = {Llm을 {이용한} {분석} Report {생성} (1)},
  date = {2024-10-14},
  url = {https://blog.zarathu.com/posts/2024-10-14-reportGeneration/},
  langid = {en}
}
For attribution, please cite this work as:
Sun Park, Young. 2024. “Llm을 이용한 분석 Report 생성 (1).” October 14, 2024. https://blog.zarathu.com/posts/2024-10-14-reportGeneration/.