---
title: "Korean text analysis with RmecabKo"
output: rmarkdown::html_vignette
vignette: >
  %\VignetteIndexEntry{Korean text analysis with RmecabKo}
  %\VignetteEngine{knitr::rmarkdown}
  %\VignetteEncoding{UTF-8}
---

```{r setup, include = FALSE}
library(RmecabKo)
backend <- tryCatch({
  probe <- RcppMeCab::pos(enc2utf8("한국어"), join = FALSE)
  if (is.list(probe)) probe <- probe[[1L]]
  any(names(probe) %in% c("NNG", "NNP"))
}, error = function(e) FALSE)
has_tidy <- requireNamespace("tidytext", quietly = TRUE) &&
  requireNamespace("dplyr", quietly = TRUE)
if (has_tidy) {
  suppressPackageStartupMessages({
    library(dplyr)
    library(tidytext)
  })
}
knitr::opts_chunk$set(collapse = TRUE, comment = "#>", eval = backend)
```

`RmecabKo` is a Korean text-analysis layer on top of the `MeCab` morphological
analyzer. The heavy lifting - the native engine and dictionary compilation -
comes from `RcppMeCab`; `RmecabKo` adds tokenizers that follow the `tokenizers`
contract, curated Korean data, user-dictionary tools, and a handful of
analysis helpers. This vignette walks through a full tidy workflow.

## Setup

Install the engine and a Korean dictionary once per machine:

```{r install, eval = FALSE}
install.packages("RcppMeCab")
RcppMeCab::download_dic("ko")
RcppMeCab::set_dic("ko")
```

## Normalizing text

`text_normalize()` is a pure, dependency-light cleanup step. It composes
Unicode (NFC), folds full-width characters, and squashes repeated characters -
all of which help the analyzer and downstream matching. It needs no backend:

```{r normalize, eval = TRUE}
text_normalize("한국어 분석 ㅋㅋㅋㅋ 정말 재밌어요!!!!")
```

## A tidy tokenization

The tokenizers take a character vector and return a list of character vectors,
so they slot directly into `tidytext::unnest_tokens()`. The package ships a
small demonstration corpus, `demo_ko`:

```{r demo}
demo_ko[1:2]
```

```{r unnest, eval = backend && has_tidy}
corpus <- tibble(doc = names(demo_ko), text = demo_ko)
tokens <- corpus |>
  unnest_tokens(word, text, token = token_nouns)
head(tokens, 8)
```

Even without a working backend, the pre-computed tokenization bundled with the
package lets us continue:

```{r fallback, eval = has_tidy}
tidy_tokens <- if (backend && exists("tokens")) {
  tokens
} else {
  readRDS(system.file("extdata", "demo_ko_tokens.rds", package = "RmecabKo"))
}
count(tidy_tokens, word, sort = TRUE) |> head(6)
```

## Removing stopwords

`stopwords_ko` is a curated table of Korean function morphemes. Filter by
surface form with an `anti_join()`, or strip whole part-of-speech classes at
the tag level with `drop_pos`:

```{r stopwords, eval = has_tidy}
tidy_tokens |>
  anti_join(data.frame(word = stopwords_ko_words()), by = "word") |>
  count(word, sort = TRUE) |>
  head(6)
```

```{r droppos}
# drop every particle and ending directly during tokenization
token_morph(demo_ko[[2]], drop_pos = stopwords_ko_tags(c("josa", "eomi")),
            simplify = TRUE)
```

## Keywords and TF-IDF

With a document column in hand, `tidytext::bind_tf_idf()` gives per-document
keyword weights; `keywords_tfidf()` offers the same without the tidy detour:

```{r tfidf, eval = has_tidy}
tidy_tokens |>
  count(doc, word) |>
  bind_tf_idf(word, doc, n) |>
  arrange(desc(tf_idf)) |>
  head(6)
```

```{r keywords}
keywords_tfidf(demo_ko, div = "nouns", top_n = 2) |> head(6)
```

## Sentiment

`lexicon_knu()` downloads and caches the KNU Korean sentiment lexicon (polarity
from -2 to 2). Joining it against tokens yields a per-document sentiment score.
The lexicon is distributed under CC BY-NC-SA (Kyungpook National University),
so it is fetched on demand rather than bundled; note the NonCommercial clause
and review its terms before use.

```{r sentiment, eval = FALSE}
senti <- lexicon_knu()
tidy_tokens |>
  inner_join(senti[senti$n_words == 1, ], by = "word") |>
  group_by(doc) |>
  summarise(score = sum(polarity))
```

## N-grams, lemmas, and concordances

Morpheme n-grams never bridge a removed stopword:

```{r ngrams}
token_ngrams(demo_ko[[1]], n = 2, div = "nouns", simplify = TRUE)
```

`token_lemma()` recovers the dictionary form of predicates, which keeps
inflected verbs and adjectives from scattering in a frequency count:

```{r lemma}
token_lemma(c("아침을 먹었다", "날씨가 좋았다"))
```

`kwic()` shows a keyword in its morpheme context:

```{r kwic}
kwic(demo_ko, "분석")
```

## Teaching the analyzer new words

When MeCab splits a name or neologism you care about, register it once and
activate it for the session. This writes to your user data directory, so it is
not run here:

```{r userdic, eval = FALSE}
dict_add_words(c("은전한닢", "카비봇"), tag = "NNP")
dict_use()
pos("카비봇 출시 소식")
dict_words()
```
