Handling Multibyte Characters and Strings in R: Fixing CSV Encoding Errors

Illustrated infographic summarizing: Handling Multibyte Characters And Strings In R Project

By Greg Nowak. Last updated 2026-09-06.

A CSV import can look fine until tolower() fails with an “invalid multibyte string” error. For a team preparing customer records, product feeds or agency reports, that can interrupt a routine delivery. A hurried fix can also remove meaningful characters from the data.

The practical approach is to identify the source encoding, read the file correctly and check representative records before continuing. Danish letters such as æ, ø and å belong in customer names and addresses. They should survive the import.

The earlier version of this article suggested switching from UTF-8 to Latin-1 and removing non-ASCII characters. Both techniques have specific uses, but neither is a general repair for broken text.

What causes multibyte string errors in R?

An encoding defines how bytes represent text. In UTF-8, a character may occupy several bytes. Problems arise when R interprets those bytes using an incompatible encoding, or when the input contains damaged or mixed text.

Start with the export settings of the system that produced the CSV. Ask for its declared encoding and keep an untouched copy of the file. Changing an import setting until the error disappears is insufficient evidence that the text is correct.

Choose the next step from the symptom, then verify it against the original data.
Symptom What to investigate Practical next step
Names look wrong immediately after import The file may have been decoded incorrectly Confirm the export encoding and reimport the original file
tolower() fails on some records Invalid bytes or inconsistent encoding Isolate affected values and inspect their source
Only certain supplier files fail Different export settings across suppliers Define an import configuration for each source
A destination requires ASCII An explicit restriction on permitted characters Create a separate export field and review the information lost

Read the CSV with its actual encoding

In base R, fileEncoding specifies the file’s encoding and enables conversion during import. The similarly named encoding argument marks strings; it does not re-encode the input. That distinction is documented in R’s data import reference.

# Use the encoding confirmed by the exporting system.
source_encoding <- "UTF-8"  # Change to "latin1" only if confirmed.

df <- read.csv(
  "customers.csv",
  fileEncoding = source_encoding,
  colClasses = "character"
)

Reading columns as text during diagnosis helps preserve identifiers with leading zeros. Convert numeric and date columns deliberately afterwards. For semicolon-separated files with decimal commas, use read.csv2().

Base R’s file conversion targets the current locale, which can limit the characters it represents. If your project uses readr, its import functions produce UTF-8 strings and let you specify the source encoding explicitly:

df <- readr::read_csv(
  "customers.csv",
  locale = readr::locale(encoding = source_encoding),
  col_types = readr::cols(.default = readr::col_character())
)

If the encoding is undocumented, readr::guess_encoding("customers.csv") can suggest candidates. Treat these as clues: the readr locale documentation shows that a plausible guess can still produce incorrect letters. Compare imported names and punctuation with known originals. If the exporter specifies Windows-1252, use that explicitly rather than assuming it is identical to Latin-1.

Convert existing strings without hiding failures

Encoding(text) reports declared encodings, not a reliable detection of the original file format. A result of "unknown" does not automatically mean damaged text; ASCII strings are normally unmarked. Assigning Encoding(text) <- "UTF-8" changes the declaration without converting the bytes. See R’s encoding reference.

If a character vector still contains bytes in a known source encoding, use iconv(). This small example deliberately constructs a Latin-1 name:

text <- c("S\xf8ren", NA_character_)
converted <- iconv(text, from = "latin1", to = "UTF-8", sub = NA)

failed <- which(!is.na(text) & is.na(converted))
if (length(failed) > 0L) {
  stop("Encoding conversion failed; review the original values.")
}

This distinguishes conversion failures from values that were already missing. Avoid sub = "" during diagnosis because it discards unconvertible bytes. Conversion behaviour varies across platforms, and a successful conversion does not prove the source encoding was correct. Those limitations are covered in R’s iconv documentation.

Do not apply a Latin-1 conversion to data already correctly imported as UTF-8. When the earlier import is suspect, return to the original file.

Apply lowercase conversion after fixing the input

Once the text is correctly decoded, create a separate field such as df$customer_name_lower <- tolower(df$customer_name). Preserve the original name for display and review.

Case conversion depends on the platform and locale, as R’s case conversion documentation explains. Check examples from the languages you actually process on the machine running the scheduled job. A lowercase name alone should not become a customer identifier.

When the original gsub command is appropriate

The original command remains useful when a destination explicitly accepts only printable ASCII:

ascii_text <- gsub('[^\x20-\x7E]', '', text)

Run it on correctly decoded text and keep the result separate. It removes everything outside ASCII space through tilde, including accented letters, emoji, tabs and line breaks. “Søren” becomes “Sren”. It deletes characters rather than transliterating them, so it is unsuitable as routine cleanup for names or addresses.

Make the fix part of the workflow

Before accepting an import, reconcile record counts, inspect warnings, compare missing values and check a small sample containing international characters. Keep those examples as repeatable checks for the next export. Document the source encoding, delimiter and person responsible for resolving failures.

If recurring CSV problems are delaying reporting or client delivery, talk to Greg about the import workflow. Bring the failing command, export settings and an anonymised sample that preserves the problematic bytes. That gives the investigation a concrete starting point.

Related on GrN.dk

Need help with this kind of work?

Talk to Greg about your data import workflow Get in touch with Greg.

Sources

Latest articles

Before buying a GPU, test one real team workflow on existing hardware. A Linux pilot can show whether quality, memory, response times, and running costs add up.

Planning a Drupal relaunch? Set clear rules for content, translations, media and old URLs, with a practical checklist for approving the migration and launch.

Use AI for your online store’s alt text with a manageable pilot: map the images, generate suggestions in Danish, and check the results in WordPress and WooCommerce.

Supplier files need more than extraction. Here’s how to check coverage, match SKUs, resolve unclear units and prices, and test product data before a catalogue import.

Shorter TLS certificates leave less room for renewal problems. Check domain validation, scheduling, deployment and the certificate your customers actually receive.

AI image credentials can disappear during routine website processing. Learn how to test your CMS, optimizer, CDN, and publishing workflow end to end.

AI-based ticket analysis can uncover recurring complaints, product defects and gaps in documentation—without the company needing yet another chatbot.

OpenAI’s X.509 workload identity can replace API keys for the right workloads. This practical framework helps teams decide where to start safely.

WordPress 7.1 helps AI agents discover and invoke site abilities. Here is how to keep exposure, authentication and permission firmly separate.

How to use AI for meeting notes and follow-up while fixed rules protect CRM data, customer matching and the sales pipeline from errors and premature changes.

Review Greg on Google

Greg Nowak Google Reviews

 

Written recommendations from Trafik og Veje, Aarhus Municipality (2011) and AgroTech (2010) — read them on LinkedIn.