{"id":13857985,"url":"https://github.com/trinker/textclean","last_synced_at":"2025-04-05T01:04:44.565Z","repository":{"id":45793935,"uuid":"49179857","full_name":"trinker/textclean","owner":"trinker","description":"Tools for cleaning and normalizing text data","archived":false,"fork":false,"pushed_at":"2021-11-01T17:04:05.000Z","size":24996,"stargazers_count":248,"open_issues_count":8,"forks_count":26,"subscribers_count":10,"default_branch":"master","last_synced_at":"2025-03-29T00:05:21.418Z","etag":null,"topics":["data-munging","emoticons","r","regex","text-analysis","text-cleaning"],"latest_commit_sha":null,"homepage":"","language":"R","has_issues":true,"has_wiki":null,"has_pages":null,"mirror_url":null,"source_name":null,"license":null,"status":null,"scm":"git","pull_requests_enabled":true,"icon_url":"https://github.com/trinker.png","metadata":{"files":{"readme":"README.Rmd","changelog":null,"contributing":null,"funding":null,"license":null,"code_of_conduct":"CODE_OF_CONDUCT.md","threat_model":null,"audit":null,"citation":null,"codeowners":null,"security":null,"support":null}},"created_at":"2016-01-07T03:56:49.000Z","updated_at":"2025-02-20T08:45:10.000Z","dependencies_parsed_at":"2022-07-17T00:30:54.049Z","dependency_job_id":null,"html_url":"https://github.com/trinker/textclean","commit_stats":null,"previous_names":[],"tags_count":3,"template":false,"template_full_name":null,"repository_url":"https://repos.ecosyste.ms/api/v1/hosts/GitHub/repositories/trinker%2Ftextclean","tags_url":"https://repos.ecosyste.ms/api/v1/hosts/GitHub/repositories/trinker%2Ftextclean/tags","releases_url":"https://repos.ecosyste.ms/api/v1/hosts/GitHub/repositories/trinker%2Ftextclean/releases","manifests_url":"https://repos.ecosyste.ms/api/v1/hosts/GitHub/repositories/trinker%2Ftextclean/manifests","owner_url":"https://repos.ecosyste.ms/api/v1/hosts/GitHub/owners/trinker","download_url":"https://codeload.github.com/trinker/textclean/tar.gz/refs/heads/master","host":{"name":"GitHub","url":"https://github.com","kind":"github","repositories_count":247271519,"owners_count":20911587,"icon_url":"https://github.com/github.png","version":null,"created_at":"2022-05-30T11:31:42.601Z","updated_at":"2022-07-04T15:15:14.044Z","host_url":"https://repos.ecosyste.ms/api/v1/hosts/GitHub","repositories_url":"https://repos.ecosyste.ms/api/v1/hosts/GitHub/repositories","repository_names_url":"https://repos.ecosyste.ms/api/v1/hosts/GitHub/repository_names","owners_url":"https://repos.ecosyste.ms/api/v1/hosts/GitHub/owners"}},"keywords":["data-munging","emoticons","r","regex","text-analysis","text-cleaning"],"created_at":"2024-08-05T03:01:53.154Z","updated_at":"2025-04-05T01:04:44.545Z","avatar_url":"https://github.com/trinker.png","language":"R","funding_links":[],"categories":["R"],"sub_categories":[],"readme":"---\ntitle: \"textclean\"\ndate: \"`r format(Sys.time(), '%d %B, %Y')`\"\noutput:\n  md_document:\n    toc: true      \n---\n\n```{r, echo=FALSE, warning=FALSE, error=FALSE}\ndesc \u003c- suppressWarnings(readLines(\"DESCRIPTION\"))\nregex \u003c- \"(^Version:\\\\s+)(\\\\d+\\\\.\\\\d+\\\\.\\\\d+)\"\nloc \u003c- grep(regex, desc)\nver \u003c- gsub(regex, \"\\\\2\", desc[loc])\npacman::p_load_current_gh('trinker/numform')\nverbadge \u003c- sprintf('\u003ca href=\"https://img.shields.io/badge/Version-%s-orange.svg\"\u003e\u003cimg src=\"https://img.shields.io/badge/Version-%s-orange.svg\" alt=\"Version\"/\u003e\u003c/a\u003e\u003c/p\u003e', ver, ver)\nverbadge \u003c- \"\"\n```\n\n[![Project Status: Active - The project has reached a stable, usable\nstate and is being actively\ndeveloped.](https://www.repostatus.org/badges/0.1.0/active.svg)](http://www.repostatus.org/#active)\n[![Build Status](https://travis-ci.org/trinker/textclean.svg?branch=master)](https://travis-ci.org/trinker/textclean)\n[![Coverage Status](https://coveralls.io/repos/trinker/textclean/badge.svg?branch=master)](https://coveralls.io/github/trinker/textclean)\n[![](https://cranlogs.r-pkg.org/badges/textclean)](https://cran.r-project.org/package=textclean)\n`r verbadge`\n\n![](tools/textclean_logo/r_textclean.png)\n\n**textclean** is a collection of tools to clean and normalize text.  Many of these tools have been taken from the **qdap** package and revamped to be more intuitive, better named, and faster.  Tools are geared at checking for substrings that are not optimal for analysis and replacing or removing them (normalizing) with more analysis friendly substrings (see Sproat, Black, Chen, Kumar, Ostendorf, \u0026 Richards, 2001, [doi:10.1006/csla.2001.0169](https://www.sciencedirect.com/science/article/pii/S088523080190169X)) or extracting them into new variables. For example, emoticons are often used in text but not always easily handled by analysis algorithms.  The `replace_emoticon()` function replaces emoticons with word equivalents. \n\nOther R packages provide some of the same functionality (e.g., **english**, **gsubfn**, **mgsub**, **stringi**, **stringr**, **qdapRegex**).  **textclean** differs from these packages in that it is designed to handle all of the common cleaning and normalization tasks with a single, consistent, pre-configured toolset (note that **textclean** uses many of these terrific packages as a backend).  This means that the researcher spends less time on munging, leading to quicker analysis.  This package is meant to be used jointly with the [**textshape**](https://github.com/trinker/textshape) package, which provides text extraction and reshaping functionality.  **textclean** works well with the [**qdapRegex**](https://github.com/trinker/qdapRegex) package which provides tooling for substring substitution and extraction of pre-canned regular expressions.  In addition, the functions of **textclean** are designed to work within the piping of the tidyverse framework by consistently using the first argument of functions as the data source. The **textclean** subbing and replacement tools are particularly effective within a `dplyr::mutate` statement. \n\n# Functions\n\nThe main functions, task category, \u0026 descriptions are summarized in the table below:\n\n\n| Function                  | Task        | Description                           | \n|---------------------------|-------------|---------------------------------------| \n| `mgsub`                   | subbing     | Multiple `gsub`                       |\n| `fgsub`                   | subbing     | Functional matching replacement `gsub`  |\n| `sub_holder`              | subbing     | Hold a value prior to a `strip`       |\n| `swap`                    | subbing     | Simultaneously swap patterns 1 \u0026 2    |\n| `strip`                   | deletion    | Remove all non word characters        |\n| `drop_empty_row`          | filter rows | Remove empty rows                     |\n| `drop_row`/`keep_row`     | filter rows | Filter rows matching a regex          |\n| `drop_NA`                 | filter rows | Remove `NA` text rows                 |\n| `drop_element`/`keep_element` | filter elements | Filter matching elements from a vector   |\n| `match_tokens`            | filter elements | Filter out tokens from strings that match a regex criteria |\n| `replace_contractions`    | replacement | Replace contractions with both words  |\n| `replace_date`            | replacement | Replace dates                         |\n| `replace_email`           | replacement | Replace emails                        |\n| `replace_emoji`           | replacement | Replace emojis with word equivalent or unique identifier |\n| `replace_emoticon`        | replacement | Replace emoticons with word equivalent               |\n| `replace_grade`           | replacement | Replace grades (e.g., \"A+\") with word equivalent     |\n| `replace_hash`            | replacement | Replace Twitter style hash tags (e.g., #rstats) |\n| `replace_html`            | replacement | Replace HTML tags and symbols  |\n| `replace_incomplete`      | replacement | Replace incomplete sentence end-marks  |\n| `replace_internet_slang`  | replacement  | Replace Internet slang with word equivalents |\n| `replace_kern`            | replacement | Replace spaces for \u003e2 letter, all cap, words containing spaces in between letters  |\n| `replace_misspelling`     | replacement | Replace misspelled words with their most likely replacement |\n| `replace_money`           | replacement | Replace money  in the form of $\\\\d+.?\\\\d{0,2}  |\n| `replace_names`           | replacement | Replace common first/last names     |\n| `replace_non_ascii`       | replacement | Replace non-ASCII with equivalent or remove   |\n| `replace_number`          | replacement | Replace common numbers                |\n| `replace_ordinal`         | replacement | Replace common ordinal number form    |\n| `replace_rating`          | replacement | Replace ratings (e.g., \"10 out of 10\", \"3 stars\") with word equivalent |\n| `replace_symbol`          | replacement | Replace common symbols                |\n| `replace_tag`             | replacement | Replace Twitter style handle tag (e.g., @trinker) |\n| `replace_time`            | replacement | Replace time stamps                   |\n| `replace_to`/`replace_from` | replacement | Remove from/to begin/end of string to/from a character(s) |\n| `replace_tokens`          | replacement | Remove or replace a vector of tokens with a single value |\n| `replace_url`             | replacement | Replace URLs                          |\n| `replace_white`           | replacement | Replace regex white space characters  |\n| `replace_word_elongation` | replacement | Replace word elongations with shortened form  |\n| `add_comma_space`         | replacement | Replace non-space after comma         |\n| `add_missing_endmark`     | replacement | Replace missing endmarks with desired symbol |\n| `make_plural`             | replacement | Add plural endings to singular noun forms |\n| `check_text`              | check       | Text report of potential issues       | \n| `has_endmark`             | check       | Check if an element has an end-mark   | \n\n# Installation\n\nTo download the development version of **textclean**:\n\nDownload the [zip ball](https://github.com/trinker/textclean/zipball/master) or [tar ball](https://github.com/trinker/textclean/tarball/master), decompress and run `R CMD INSTALL` on it, or use the **pacman** package to install the development version:\n\n```r\nif (!require(\"pacman\")) install.packages(\"pacman\")\npacman::p_load_gh(\n    \"trinker/lexicon\",    \n    \"trinker/textclean\"\n)\n```\n\n# Contact\n\nYou are welcome to:    \n- submit suggestions and bug-reports at: \u003chttps://github.com/trinker/textclean/issues\u003e    \n\n\n# Contributing\n\nContributions are welcome from anyone subject to the following rules:\n\n- Abide by the [code of conduct](CONDUCT.md).\n- Follow the style conventions of the package (indentation, function \u0026 argument naming, commenting, etc.)\n- All contributions must be consistent with the package license (GPL-2)\n- Submit contributions as a pull request. Clearly state what the changes are and try to keep the number of changes per pull request as low as possible.\n- If you make big changes, add your name to the 'Author' field.\n\n# Demonstration\n\n## Load the Packages/Data\n\n```{r, message=FALSE}\nlibrary(dplyr)\nlibrary(textshape)\nlibrary(lexicon)\nlibrary(textclean)\n```\n\n## Check Text\n\nOne of the most useful tools in **textclean** is `check_text` which scans text variables and reports potential problems.  Not all potential problems are definite problems for analysis but the report provides an overview of what may need further preparation.  The report also provides suggested functions for the reported problems.  The report provides information on the following:\n\n```{r, results = 'asis', comment = NA, echo = FALSE}\ncat(sapply(textclean:::.checks, function(x){\n\n    frame \u003c- ifelse(x$is_meta, \"1. **%s** - Text variable that %s\", \"1. **%s** - Text elements that %s\")\n    sprintf(frame, x$fun, x$problem)\n\n}), sep = '\\n')\n```\n\nNote that `check_text` is running multiple checks and may be slower on larger texts.  The user may provide a sample of text for review or use the `checks` argument to specify the exact checks to conduct and thus limit the compute time.\n\nHere is an example:\n\n```{r}\nx \u003c- c(\"i like\", \"\u003cp\u003ei want. \u003c/p\u003e. thet them ther .\", \"I am ! that|\", \"\", NA, \n    \"\u0026quot;they\u0026quot; they,were there\", \".\", \"   \", \"?\", \"3;\", \"I like goud eggs!\", \n    \"bi\\xdfchen Z\\xfcrcher\", \"i 4like...\", \"\\\\tgreat\",  \"She said \\\"yes\\\"\")\nEncoding(x) \u003c- \"latin1\"\nx \u003c- as.factor(x)\ncheck_text(x)\n```\n\nAnd if all is well the user should be greeted by a cow:\n\n```{r}\ny \u003c- c(\"A valid sentence.\", \"yet another!\")\ncheck_text(y)\n```\n\n\n## Row Filtering\n\nIt is useful to drop/remove empty rows or unwanted rows (for example the researcher dialogue from a transcript).  The `drop_empty_row` \u0026 `drop_row` do empty row do just this.  First I'll demo the removal of empty rows.\n\n\n```{r}\n## create a data set wit empty rows\n(dat \u003c- rbind.data.frame(DATA[, c(1, 4)], matrix(rep(\" \", 4), \n    ncol =2, dimnames=list(12:13, colnames(DATA)[c(1, 4)]))))\n\ndrop_empty_row(dat)\n```\n\nNext we drop out rows.  The `drop_row` function takes a data set, a column (named or numeric position) and regex terms to search for.  The `terms` argument takes regex(es) allowing for partial matching.  `terms` is case sensitive  but can be changed via the `ignore.case` argument.\n\n```{r}\ndrop_row(dataframe = DATA, column = \"person\", terms = c(\"sam\", \"greg\"))\ndrop_row(DATA, 1, c(\"sam\", \"greg\"))\nkeep_row(DATA, 1, c(\"sam\", \"greg\"))\ndrop_row(DATA, \"state\", c(\"Comp\"))\ndrop_row(DATA, \"state\", c(\"I \"))\ndrop_row(DATA, \"state\", c(\"you\"), ignore.case = TRUE)\n```\n\n## Stripping\n\nOften it is useful to remove all non relevant symbols and case from a text (letters, spaces, and apostrophes are retained).  The `strip` function accomplishes this.  The `char.keep` argument allows the user to retain characters.\n\n\n```{r}\nstrip(DATA$state)\nstrip(DATA$state, apostrophe.remove = TRUE)\nstrip(DATA$state, char.keep = c(\"?\", \".\"))\n```\n\n\n## Subbing\n\n### Multiple Subs\n\n`gsub` is a great tool but often the user wants to replace a vector of elements with another vector.  `mgsub` allows for a vector of patterns and replacements.  Note that the first argument of `mgsub` is the data, not the `pattern` as is standard with base R's `gsub`.  This allows `mgsub` to be used in a **magrittr** pipeline more easily.  Also note that by default `fixed = TRUE`.  This means the search `pattern` is not a regex per-se.  This makes the replacement much faster when a regex search is not needed.  `mgsub` also reorders the patterns to ensure patterns contained within patterns don't over write the longer pattern.  For example if the pattern `c('i', 'it')` is given the longer `'it'` is replaced first (though `order.pattern = FALSE` can be used to negate this feature).\n\n```{r}\nmgsub(DATA$state, c(\"it's\", \"I'm\"), c(\"\u003c\u003cit is\u003e\u003e\", \"\u003c\u003cI am\u003e\u003e\"))\nmgsub(DATA$state, \"[[:punct:]]\", \"\u003c\u003cPUNCT\u003e\u003e\", fixed = FALSE)\nmgsub(DATA$state, c(\"i\", \"it\"), c(\"\u003c\u003cI\u003e\u003e\", \"[[IT]]\"))\nmgsub(DATA$state, c(\"i\", \"it\"), c(\"\u003c\u003cI\u003e\u003e\", \"[[IT]]\"), order.pattern = FALSE)\n```\n\n#### Safe Substitutions\n\nThe default behavior of `mgsub` is optimized for speed.  This means that it is very fast at multiple substitutions and in most cases works efficiently.  However, it is not what Mark Ewing describes as \"safe\" substitution.  In his vignette for the [**mgsub**](https://github.com/bmewing/mgsub) package, Mark defines \"safe\" as:\n\n\u003e 1. Longer matches are preferred over shorter matches for substitution first\n\u003e 2. No placeholders are used so accidental string collisions don't occur\n\nBecause safety is sometimes required, `textclean::mgsub` provides a `safe` argument using the **mgsub** package as the backend.   In addition to the `safe` argument the `mgsub_regex_safe` function is available to make the usage more explicit.  The safe mode comes at the cost of speed.\n\n\n\n\n```{r}\nx \u003c- \"Dopazamine is a fake chemical\"\npattern \u003c- c(\"dopazamin\", \"do.*ne\")\nreplacement \u003c- c(\"freakout\", \"metazamine\")\n```\n\n```{r}\n## Unsafe\nmgsub(x, pattern, replacement, ignore.case=TRUE, fixed = FALSE)\n## Safe\nmgsub(x, pattern, replacement, ignore.case=TRUE, fixed = FALSE, safe = TRUE)\n## Or alternatively\nmgsub_regex_safe(x, pattern, replacement, ignore.case=TRUE)\n```\n\n\n```{r}\nx \u003c- \"hey, how are you?\"\npattern \u003c- c(\"hey\", \"how\", \"are\", \"you\")\nreplacement \u003c- c(\"how\", \"are\", \"you\", \"hey\")\n```\n\n```{r}\n## Unsafe\nmgsub(x, pattern,replacement)\n## Safe\nmgsub_regex_safe(x, pattern,replacement)\n```\n\n\n\n### Match, Extract, Operate, Replacement Subs\n\nAgain, `gsub` is a great tool but sometimes the user wants to match a pattern, extract that pattern, operate a function over that pattern, and then replace the original match.  The `fgsub` function allows the user to perform this operation.  It is a stripped down version of `gsubfn` from the **gsubfn** package.  For more versatile needs please see the **gsubfn** package.\n\nIn this example the regex looks for words that contain a lower case letter followed by the same letter at least 2 more times.  It then extracts these words, splits them appart into letters, reverses the string, pastes them back together, wraps them with double angle braces, and then puts them back at the original locations.\n\n\n```{r}\nfgsub(\n    x = c(NA, 'df dft sdf', 'sd fdggg sd dfhhh d', 'ddd'),\n    pattern = \"\\\\b\\\\w*([a-z])(\\\\1{2,})\\\\w*\\\\b\",\n    fun = function(x) {paste0('\u003c\u003c', paste(rev(strsplit(x, '')[[1]]), collapse =''), '\u003e\u003e')}\n)\n```\n\nIn this example we extract numbers, strip out non-digits, coerce them to numeric, cut them in half, round up to the closest integer, add the commas back, and replace back into the original locations.\n\n```{r}\nfgsub(\n    x = c(NA, 'I want 32 grapes', 'he wants 4 ice creams', 'they want 1,234,567 dollars'),\n    pattern = \"[\\\\d,]+\",\n    fun = function(x) {prettyNum(ceiling(as.numeric(gsub('[^0-9]', '', x))/2), big.mark = ',')}\n)\n```\n\n### Stashing Character Pre-Sub\n\nThere are times the user may want to stash a set of characters before subbing out and then return the stashed characters.  An example of this is when a researcher wants to remove punctuation but not emoticons.  The `subholder` function provides tooling to stash the emoticons, allow a punctuation stripping, and then return the emoticons.  First I'll create some fake text data with emoticons, then stash the emoticons (using a unique text key to hold their place), then I'll strip out the punctuation, and last put the stashed emoticons back.\n\n\n```{r}\n(fake_dat \u003c- paste(hash_emoticons[1:11, 1, with=FALSE][[1]], DATA$state))\n(m \u003c- sub_holder(fake_dat, hash_emoticons[[1]]))\n(m_stripped \u003c-strip(m$output))\nm$unhold(m_stripped)\n```\n\nOf course with clever regexes you can achieve the same thing:\n\n```{r}\nord_emos \u003c- hash_emoticons[[1]][order(nchar(hash_emoticons[[1]]))]\n\n## This step ensures that longer strings are matched first but can \n## fail in cases that use quantifiers.  These can appear short but in\n## reality can match long strings and would be ordered last in the \n## replacement, meaning that the shorter regex took precedent.\nemos \u003c- paste(\n    gsub('([().\\\\|[{}^$*+?])', '\\\\\\\\\\\\1', ord_emos),\n    collapse = '|'\n)\n\ngsub(\n    sprintf('(%s)(*SKIP)(*FAIL)|[^\\'[:^punct:]]', emos), \n    '', \n    fake_dat, \n    perl = TRUE\n)\n```\n\nThe pure regex approach can be a bit trickier (less safe) and more difficult to reason about.  It also relies on the less general `(*SKIP)(*FAIL)` backtracking control verbs that are only implemented in a few applications like Perl \u0026 PCRE.  Still, it's nice to see an alternative regex approach for comparison.\n\n## Replacement\n\n**textclean** contains tools to replace substrings within text with other substrings that may be easier to analyze.  This section outlines the uses of these tools.  \n\n### Contractions\n\nSome analysis techniques require contractions to be replaced with their multi-word forms (e.g., \"I'll\" -\u003e \"I will\").  `replace_contrction` provides this functionality.\n\n```{r}\nx \u003c- c(\"Mr. Jones isn't going.\",  \n    \"Check it out what's going on.\",\n    \"He's here but didn't go.\",\n    \"the robot at t.s. wasn't nice\", \n    \"he'd like it if i'd go away\")\n\nreplace_contraction(x)\n```\n\n\n### Dates\n\n```{r}\nx \u003c- c(NA, '11-16-1980 and 11/16/1980', \"and 2017-02-08 but then there's 2/8/2017 too\")\n\nreplace_date(x)\nreplace_date(x, replacement = '\u003c\u003cDATE\u003e\u003e')\n```\n\n### Emojis\n\nSimilar to emoticons, emoji tokens may be ignored if they are not in a computer readable form.  `replace_emoji` replaces emojis with their word forms equivalents.\n\n```{r}\nx \u003c- read.delim(system.file(\"docs/r_tweets.txt\", package = \"textclean\"), \n    stringsAsFactors = FALSE)[[3]][1:3]\n\nx\n```\n\n```{r}\nreplace_emoji(x)\n```\n\n\n### Emoticons\n\nSome analysis techniques examine words, meaning emoticons may be ignored.  `replace_emoticon` replaces emoticons with their word forms equivalents.\n\n```{r}\nx \u003c- c(\n    \"text from: http://www.webopedia.com/quick_ref/textmessageabbreviations_02.asp\",\n    \"... understanding what different characters used in smiley faces mean:\",\n    \"The close bracket represents a sideways smile  )\",\n    \"Add in the colon and you have sideways eyes   :\",\n    \"Put them together to make a smiley face  :)\",\n    \"Use the dash -  to add a nose   :-)\",\n    \"Change the colon to a semi-colon ; and you have a winking face ;)  with a nose  ;-)\",\n    \"Put a zero 0 (halo) on top and now you have a winking, smiling angel 0;) with a nose 0;-)\",\n    \"Use the letter 8 in place of the colon for sunglasses 8-)\",\n    \"Use the open bracket ( to turn the smile into a frown  :-(\"\n)\n\nreplace_emoticon(x)\n```\n\n\n### Grades\n\nIn analysis where grades may be discussed it may be useful to convert the letter forms into word meanings.  The `replace_grade` can be used for this task.\n\n```{r}\ntext \u003c- c(\n    \"I give an A+\",\n    \"He deserves an F\",\n    \"It's C+ work\",\n    \"A poor example deserves a C!\"\n)\nreplace_grade(text)\n```\n\n### HTML\n\nSometimes HTML tags and symbols stick around like pesky gnats.  The `replace_html` function makes light work of them.\n\n```{r}\nx \u003c- c(\n    \"\u003cbold\u003eRandom\u003c/bold\u003e text with symbols: \u0026nbsp; \u0026lt; \u0026gt; \u0026amp; \u0026quot; \u0026apos;\",\n    \"\u003cp\u003eMore text\u003c/p\u003e \u0026cent; \u0026pound; \u0026yen; \u0026euro; \u0026copy; \u0026reg;\"\n)\n\nreplace_html(x)\n```\n\n### Incomplete Sentences\n\nSometimes an incomplete sentence is denoted with multiple end marks or no punctuation at all.  `replace_incomplete` standardizes these sentences with a pipe (`|`) endmark (or one of the user's choice).\n\n```{r}\nx \u003c- c(\"the...\",  \"I.?\", \"you.\", \"threw..\", \"we?\")\nreplace_incomplete(x)\nreplace_incomplete(x, '...')\n```\n\n\n### Internet Slang\n\nOften in informal written and spoken communication (e.g., Twitter, texting, Facebook, etc.) people use Internet slang, shorter abbreviations and acronyms, to replace longer word sequences.  These replacements may obfuscate the meaning when the machine attempts to analyze the text.  The `replace_internet_slang` function replaces the slang with longer word equivalents that are more easily analyzed by machines.\n\n```{r}\nx \u003c- c(\n    \"TGIF and a big w00t!  The weekend is GR8!\",\n    \"NP it was my pleasure: EOM\",\n    'w8...this n00b needs me to say LMGTFY...lol.',\n    NA\n)\n\nreplace_internet_slang(x)\n```\n\n\n### Kerning\n\nIn typography kerning is the adjustment of spacing.  Often, in informal writing, adding manual spaces (a form of kerning) coupled with all capital letters is  used for emphasis (e.g., `\"She's the B O M B!\"`).  These word forms would look like noise in most analysis and would likely be removed as a stopword when in fact they likely carry a great deal of meaning.  The `replace_kern` function looks for 3 or more consecutive capital letters with spaces in between and removes the spaces.  \n\n\n```{r}\nx \u003c- c(\n    \"Welcome to A I: the best W O R L D!\",\n    \"Hi I R is the B O M B for sure: we A G R E E indeed.\",\n    \"A sort C A T indeed!\",\n    NA\n)\n\nreplace_kern(x)\n```\n\n### Money\n\nThere are times one may want to replace money mentions with text or normalized versions.  The `replace_money` function is designed to complete this task.\n\n```{r}\nx \u003c- c(NA, '$3.16 into \"three dollars, sixteen cents\"', \"-$20,333.18 too\", 'fff')\n \nreplace_money(x)\n```\n\n```{r}\nreplace_money(x, replacement = '\u003c\u003cMONEY\u003e\u003e')\n```\n\n\n### Names\n\nOften one will want to standardize text by removing first and last names.  The `replace_names` function quickly removes/replaces common first and last names.  This can be made more targeted by feeding a vector of names extracted via a named entity extractor.\n\n```{r}\nx \u003c- c(\n    \"Mary Smith is not here\",\n     \"Karen is not a nice person\",\n     \"Will will do it\",\n    NA\n)\n \nreplace_names(x)\nreplace_names(x, replacement = '\u003c\u003cNAME\u003e\u003e')\n```\n\n### Non-ASCII Characters\n\nR can choke on non-ASCII characters.  They can be re-encoded but the new encoding may lack interpretability (e.g., \u0026cent; may be converted to `\\xA2` which is not easily understood or likely to be matched in a hash look up).  `replace_non_ascii` attempts to replace common non-ASCII characters with a text representation (e.g., \u0026cent; becomes \"cent\")  Non recognized non-ASCII characters are simply removed (unless `remove.nonconverted = FALSE`).\n\n\n```{r}\nx \u003c- c(\n    \"Hello World\", \"6 Ekstr\\xf8m\", \"J\\xf6reskog\", \"bi\\xdfchen Z\\xfcrcher\",\n    'This is a \\xA9 but not a \\xAE', '6 \\xF7 2 = 3', 'fractions \\xBC, \\xBD, \\xBE',\n    'cows go \\xB5', '30\\xA2'\n)\nEncoding(x) \u003c- \"latin1\"\nx\n\nreplace_non_ascii(x)\nreplace_non_ascii(x, remove.nonconverted = FALSE)\n```\n\n### Numbers\n\nSome analysis requires numbers to be converted to text form.  `replace_number` attempts to perform this task.  `replace_number` handles comma separated numbers as well.\n\n```{r}\nx \u003c- c(\"I like 346,457 ice cream cones.\", \"They are 99 percent good\")\ny \u003c- c(\"I like 346457 ice cream cones.\", \"They are 99 percent good\")\nreplace_number(x)\nreplace_number(y)\nreplace_number(x, num.paste = TRUE)\nreplace_number(x, remove=TRUE)\n```\n\n### Ratings\n\nSome texts use ratings to convey satisfaction with a particular object.  The `replace_rating` function replaces the more abstract rating with word equivalents.\n\n```{r}\nx \u003c- c(\"This place receives 5 stars for their APPETIZERS!!!\",\n     \"Four stars for the food \u0026 the guy in the blue shirt for his great vibe!\",\n     \"10 out of 10 for both the movie and trilogy.\",\n     \"* Both the Hot \u0026 Sour \u0026 the Egg Flower Soups were absolutely 5 Stars!\",\n     \"For service, I give them no stars.\", \"This place deserves no stars.\",\n     \"10 out of 10 stars.\",\n     \"My rating: just 3 out of 10.\",\n     \"If there were zero stars I would give it zero stars.\",\n     \"Rating: 1 out of 10.\",\n     \"I gave it 5 stars because of the sound quality.\",\n     \"If it were possible to give them 0/10, they'd have it.\"\n)\n\nreplace_rating(x)\n```\n\n\n### Ordinal Numbers\n\nAgain, some analysis requires numbers, including ordinal numbers, to be converted to text form.  `replace_ordinal` attempts to perform this task for ordinal number 1-100 (i.e., 1st - 100th).  \n\n\n```{r}\nx \u003c- c(\n    \"I like the 1st one not the 22nd one.\", \n    \"For the 100th time stop those 3 things!\",\n    \"I like the 3rd 1 not the 12th 1.\"\n)\nreplace_ordinal(x)\nreplace_ordinal(x, TRUE)\nreplace_ordinal(x, remove = TRUE)\nreplace_number(replace_ordinal(x))\n```\n\n\n### Symbols\n\nText often contains short-hand representations of words/phrases.  These symbols may contain analyzable information but in the symbolic form they cannot be parsed.  The `replace_symbol` function attempts to replace the symbols `c(\"$\", \"%\", \"#\", \"@\", \"\u0026 \"w/\")` with their word equivalents.\n\n```{r}\nx \u003c- c(\"I am @ Jon's \u0026 Jim's w/ Marry\", \n    \"I owe $41 for food\", \n    \"two is 10% of a #\"\n)\nreplace_symbol(x)\n```\n\n### Time Stamps\n\nOften times the researcher will want to replace times with a text or normalized version.  The `replace_time` function works well for this task.  Notice that replacement takes a function that can operate on the extracted pattern.\n\n```{r}\nx \u003c- c(\n    NA, '12:47 to \"twelve forty-seven\" and also 8:35:02',\n    'what about 14:24.5', 'And then 99:99:99?'\n)\n\n## Textual: Word version\nreplace_time(x)\n\n## Normalization: \u003c\u003cTIME\u003e\u003e\nreplace_time(x, replacement = '\u003c\u003cTIME\u003e\u003e')\n\n## Normalization: hh:mm:ss or hh:mm\nreplace_time(x, replacement = function(y){\n        z \u003c- unlist(strsplit(y, '[:.]'))\n        z[1] \u003c- 'hh'\n        z[2] \u003c- 'mm'\n        if(!is.na(z[3])) z[3] \u003c- 'ss'\n        textclean::glue_collapse(z, ':')\n    }\n)\n\n## Textual: Word version (forced seconds)\nreplace_time(x, replacement = function(y){\n        z \u003c- replace_number(unlist(strsplit(y, '[:.]')))\n        z[3] \u003c- paste0('and ', ifelse(is.na(z[3]), '0', z[3]), ' seconds')\n        paste(z, collapse = ' ')\n    }\n)\n\n```\n\n### Tokens\n\nOften an analysis requires converting tokens of a certain type into a common form or removing them entirely.  The `mgsub` function can do this task, however it is regex based and time consuming when the number of tokens to replace is large.  For example, one may want to replace all proper nouns that are first names with the word name.  The `replace_token` provides a fast way to replace a group of tokens with a single replacement.\n\nThis example shows a use case for `replace_token`:\n\n```{r}\n## Set Up the Tokens to Replace\nnms \u003c- gsub(\"(^.)(.*)\", \"\\\\U\\\\1\\\\L\\\\2\", lexicon::common_names, perl = TRUE)\nhead(nms)\n\n## Set Up the Data\nx \u003c- textshape::split_portion(sample(c(sample(lexicon::grady_augmented, 20000), \n    sample(nms, 10000, TRUE))), n.words = 12)\nx$text.var \u003c- paste0(x$text.var, sample(c('.', '!', '?'), length(x$text.var), TRUE))\nhead(x$text.var)\n\nhead(replace_tokens(x$text.var, nms, 'NAME'))\n```\n\n\n\nThis demonstration shows how fast token replacement can be with `replace_token`:\n\n```{r}\n## mgsub\ntic \u003c- Sys.time()\nhead(mgsub(x$text.var, nms, \"NAME\"))\n(toc \u003c- Sys.time() - tic)\n\n## replace_tokens\ntic \u003c- Sys.time()\nhead(replace_tokens(x$text.var, nms, \"NAME\"))\n(toc \u003c- Sys.time() - tic)\n```\n\n```{r, echo = FALSE}\ntic \u003c- Sys.time()\nout \u003c- replace_tokens(rep(x$text.var, 20), nms, \"NAME\")\ntoc \u003c- Sys.time() - tic\n```\n\nNow let's amp it up with 20x more text data.  That's `r f_comma(length(x$text.var) * 20)` rows of text (`r f_comma(sum(stringi::stri_count_words(x$text.var))*20)` words) and `r f_comma(length(nms))` replacement tokens in `r round(toc, 1)` seconds.\n\n```\ntic \u003c- Sys.time()\nout \u003c- replace_tokens(rep(x$text.var, 20), nms, \"NAME\")\n(toc \u003c- Sys.time() - tic)\n```\n```{r, echo=FALSE}\ntoc\n```\n\n\n### White Space\n\nRegex white space characters (e.g., `\\n`, `\\t`, `\\r`) matched by `\\s` may impede analysis.  These can be replaced with a single space `\" \"` via the `replace_white` function.\n\n```{r}\nx \u003c- \"I go \\r\n    to   the \\tnext line\"\nx\ncat(x)\nreplace_white(x)\n```\n\n### Word Elongation\n\nIn informal writing people may use a form of text embellishment to emphasize or alter word meanings called elongation (a.k.a. \"word lengthening\").  For example, the use of \"Whyyyyy\" conveys frustration.  Other times the usage may be to be more sexy (e.g., \"Heyyyy there\").  Other times it may be used for emphasis (e.g., \"This is so gooood\").\n\nThe `replace_word_elongation` function replaces these un-normalized forms with the most likely normalized form.  The `impart.meaning` argument can replace a short list of known elongations with semantic replacements.\n\n```{r}\nx \u003c- c('look', 'noooooo!', 'real coooool!', \"it's sooo goooood\", 'fsdfds',\n    'fdddf', 'as', \"aaaahahahahaha\", \"aabbccxccbbaa\", 'I said heyyy!',\n    \"I'm liiiike whyyyyy me?\", \"Wwwhhatttt!\", NA)\n\nreplace_word_elongation(x)\nreplace_word_elongation(x, impart.meaning = TRUE)\n```\n\n","project_url":"https://awesome.ecosyste.ms/api/v1/projects/github.com%2Ftrinker%2Ftextclean","html_url":"https://awesome.ecosyste.ms/projects/github.com%2Ftrinker%2Ftextclean","lists_url":"https://awesome.ecosyste.ms/api/v1/projects/github.com%2Ftrinker%2Ftextclean/lists"}