6. Character vectors¶
This open-access textbook is, and will remain, freely available for everyone’s enjoyment (also in PDF; a paper copy can also be ordered). It is a non-profit project. Although available online, it is a whole course, and should be read from the beginning to the end. Refer to the Preface for general introductory remarks. Any bug/typo reports/fixes are appreciated. Make sure to check out Minimalist Data Wrangling with Python [27], too.
Text is a universal, portable, economical, and efficient means of interacting between humans and computers as well as exchanging data between programs or APIs. This book is 99% made of text. And, wow, how much valuable knowledge is in it, innit?
6.1. Creating character vectors¶
6.1.1. Inputting individual strings¶
Specific character strings are delimited by a pair of either double or single quotes (apostrophes).
"a string"
## [1] "a string"
'another string' # and, of course, neither 'like this" nor "like this'
## [1] "another string"
The only difference between these two is
that we cannot directly include, e.g.,
an apostrophe in a single quote-delimited string.
On the other hand, "'tis good ol' spam"
and 'I "love" bacon'
are both okay.
To embrace characters whose inclusion might otherwise
be difficult or impossible, we may always employ the so-called
escape sequences.
R uses the backslash, “\
”, as the escape character. In particular:
\"
inputs a double quote,\'
generates a single quote,\\
includes a backslash,\n
endows a new line.
(x <- "I \"love\" bacon\n\\\"/")
## [1] "I \"love\" bacon\n\\\"/"
The print function (which was implicitly called to display
the above object) does not reveal the special meaning of the escape
sequences. Instead, print outputs strings in the same way that
we ourselves would follow when inputting them.
The number of characters in x
is 18, and not 23:
nchar(x)
## [1] 18
To display the string as-it-really-is, we call cat:
cat(x, sep="\n")
## I "love" bacon
## \"/
In raw character constants, the backslash character’s special
meaning is disabled. They can be entered using the notation
like r"(...)"
, r"{...}"
, or r"[...]"
; see help("Quotes")
.
These can be useful when inputting
regular expressions (Section 6.2.4).
x <- r"(spam\n\\\"maps)" # also: r"-(...)-", r"--(...)--", etc.
print(x)
## [1] "spam\\n\\\\\\\"maps"
cat(x, sep="\n")
## spam\n\\\"maps
Furthermore, the string version of the missing value marker
is NA_character_
.
Note
(*) The Unicode standard 15.1 (version dated September 2023) defines 149 813 characters, i.a., letters from different scripts, mathematical symbols, and emojis. Each is assigned a unique numeric identifier; see the Unicode Character Code Charts. For example, the inverted exclamation mark (see the Latin-1 Supplement section therein) has been mapped to the hexadecimal code 0xA1 (or 161 decimally). Knowing this magic number permits us to specify a Unicode code point using one of the following escape sequences:
\uxxxx
– codes using four hexadecimal digits,\Uxxxxxxxx
– codes using eight hexadecimal digits.
For instance:
cat("!\u00a1!\U000000a1!", sep="\n")
## !¡!¡!
All R installations allow for working with Unicode strings.
More precisely, they support dealing with UTF-8, being
a super-encoding that is native to most UNIX-like boxes,
including GNU/Linux and m**OS.
Other operating systems may use some 8-bit encoding as the system one
(e.g., latin1
or cp1252
), but they can be
mixed with Unicode seamlessly; see help("Encoding")
,
help("iconv")
, and [26] for discussion.
Nevertheless, certain output devices (web browsers, LaTeX renderers, text terminals) might fail to display some Unicode characters, e.g., because of missing fonts. However, as far as processing character data is concerned, this does not matter because R does it with its eyes closed. For example:
cat("\U0001f642\u2665\u0bb8\U0001f923\U0001f60d\u2307", sep="\n")
## 🙂♥ஸ🤣😍⌇
In the PDF version of this adorable book, the Unicode glyphs are not rendered correctly for some reason. However, its HTML variant, generated from the same source files, should be displayed by most web browsers properly.
Note
(*) Some output devices may support the following codes that control the position of the caret (text cursor):
\b
inserts a backspace (moves cursor one column to the left),\t
implants a tabulator (advances to the next tab stop, e.g., a multiply of four or eight text columns),\r
injects a carriage return (move to the beginning of the current line).
cat("abc\bd\tef\rg\nhij", sep="\n")
## gbd ef
## hij
These can be used on unbuffered outputs like stderr to display the status of the current operation, for instance, an animated textual progress bar, the print-out of the ETA, or the percentage of work completed.
Further, certain terminals can also understand
the ECMA-48/ANSI-X3.64 escape sequences of the form \u001b[...
to control the cursor’s position, text colour, and even style.
For example, \u001b[1;31m
outputs red text in bold font
and \u001b[0m
resets the settings to default.
We recommend giving, e.g., cat("\u001b[1;31mspam\u001b[0m")
or cat("\u001b[5;36m\u001b[Abacon\u001b[Espam\u001b[0m")
a try.
6.1.2. Many strings, one object¶
Less trivial character vectors (meaning, of length greater than one) can be constructed by means of, e.g., c or rep[1].
(x <- c(rep("spam", 3), "bacon", NA_character_, "spam"))
## [1] "spam" "spam" "spam" "bacon" NA "spam"
Thus, a character vector is, in fact, a sequence of sequences of characters[2]. As usual, the total number of strings can be fetched via the length function. However, the length of each string may be read with the vectorised nchar.
length(x) # how many strings?
## [1] 6
nchar(x) # the number of code points in each string
## [1] 4 4 4 5 NA 4
6.1.3. Concatenating character vectors¶
paste can be used to concatenate (join) the corresponding elements of two or more character vectors:
paste(c("a", "b", "c"), c("1", "2", "3")) # sep=" " by default
## [1] "a 1" "b 2" "c 3"
paste(c("a", "b", "c"), c("1", "2", "3"), sep="") # see also paste0
## [1] "a1" "b2" "c3"
The function is deeply vectorised (but note the lack of a warning about the partial recycling):
paste(c("a", "b", "c"), 1:5, c("!", "?")) # coercion of numeric to character
## [1] "a 1 !" "b 2 ?" "c 3 !" "a 4 ?" "b 5 !"
We can also collapse (flatten, aggregate) a sequence of strings into a single string:
paste(c("a", "b", "c", "d"), collapse=",")
## [1] "a,b,c,d"
paste(c("a", "b", "c", "d"), 1:2, sep="", collapse="")
## [1] "a1b2c1d2"
Perhaps for convenience, alas, paste treats missing values differently from most other vectorised functions:
paste(c("A", NA_character_, "B"), "!", sep="")
## [1] "A!" "NA!" "B!"
6.1.4. Formatting objects¶
Strings can also arise by converting other-typed R objects into text. For example, the quite customisable (see Chapter 10) format function prepares data for display in dynamically generated reports.
x <- c(123456.789, -pi, NaN)
format(x)
## [1] "123456.7890" " -3.1416" " NaN"
cat(format(x, digits=8, scientific=FALSE, drop0trailing=TRUE), sep="\n")
## 123456.789
## -3.1415927
## NaN
Moreover, sprintf is a workhorse for turning possibly many atomic
vectors into strings.
Its first argument is a format string. Special escape sequences starting
with the per cent sign, “%
”, serve as placeholders for the actual values.
For instance, “%s
” is replaced with a string and “%f
” with
a floating point value taken from further arguments.
sprintf("%s%s", "a", c("X", "Y", "Z")) # like paste(...)
## [1] "aX" "aY" "aZ"
sprintf("key=%s, value=%f", c("spam", "eggs"), c(100000, 0))
## [1] "key=spam, value=100000.000000" "key=eggs, value=0.000000"
The numbers’ precision, strings’ widths and justification, etc.,
can be customised, e.g., “%6.2f
” is a number that, when converted
to text, will occupy six text columns[3],
with two decimal digits of precision.
sprintf("%10s=%6.2f%%", "rate", 2/3*100) # "%%" renders the per cent sign
## [1] " rate= 66.67%"
sprintf("%.*f", 1:5, pi) # variable precision
## [1] "3.1" "3.14" "3.142" "3.1416" "3.14159"
Also, e.g., “%1$s
”, “%2$s
”, …
inserts the first, second, … argument as text.
sprintf("%1$s, %2$s, %1$s, and %1$s", "spam", "bacon") # numbered argument
## [1] "spam, bacon, spam, and spam"
Read help("sprintf")
(highly recommended!).
6.1.5. Reading text data from files¶
Given a raw text file, readLines loads it into memory and represents it as a character vector, with each line stored in a separate string.
head(readLines(
"https://github.com/gagolews/teaching-data/raw/master/README.md"
))
## [1] "# Prof. [Marek](https://www.gagolewski.com)'s Data for Teaching"
## [2] ""
## [3] "> *See the comment lines within the files themselves for"
## [4] "> a detailed description of each dataset.*"
## [5] ""
## [6] "*Good* datasets are actually hard to find!"
writeLines is its counterpart.
There is also an option to read or write parts of files at a time
using file connections which we mention in Section 8.3.5.
Moreover, cat(..., append=TRUE)
can be used
to create a text file incrementally.
6.2. Pattern searching¶
6.2.1. Comparing whole strings¶
We have already reviewed a couple of ways to compare strings as a whole. For instance, the `==` operator implements elementwise testing:
c("spam", "spam", "bacon", "eggs") == c("spam", "eggs") # recycling rule
## [1] TRUE FALSE FALSE TRUE
In Section 5.4.1, we introduced the match function and its derivative, the `%in%` operator. They are vectorised in a different way:
match(c("spam", "spam", "bacon", "eggs"), c("spam", "eggs"))
## [1] 1 1 NA 2
c("spam", "spam", "bacon", "eggs") %in% c("spam", "eggs")
## [1] TRUE TRUE FALSE TRUE
Note
(*) match relies on a simple, bytewise comparison of the corresponding code points. It might not be valid in natural language processing activities, e.g., where the German word groß should be equivalent to gross [18]. Moreover, in the rare situations where we read Unicode-unnormalised data, canonically equivalent strings may be considered different; see [17].
6.2.2. Partial matching¶
When only a consideration of the initial part of each string is required, we can call:
startsWith(c("s", "spam", "spamtastic", "spontaneous", "spoon"), "spam")
## [1] FALSE TRUE TRUE FALSE FALSE
If we provide many prefixes, the above function will be applied elementwisely, just like the `==` operator.
On the other hand, charmatch performs a partial matching of strings. It is an each-vs-all version of startsWith:
charmatch(c("s", "sp", "spam", "spams", "eggs", "bacon"), c("spam", "eggs"))
## [1] 1 1 1 NA 2 NA
charmatch(c("s", "sp", "spam", "spoo", "spoof"), c("spam", "spoon"))
## [1] 0 0 1 2 NA
Note that 0
designates that there was an ambiguous match.
Note
(*) In Section 9.4.7, we discuss match.arg, which a few R functions rely on when they need to select a value from a range of possible choices. Furthermore, Section 9.3.2 and Section 15.4.4 mention the (discouraged) partial matching of list labels and function argument names.
6.2.3. Matching anywhere within a string¶
Fixed patterns can also be searched for anywhere within character strings using grepl:
x <- c("spam", "y spammite spam", "yummy SPAM", "sram")
grepl("spam", x, fixed=TRUE) # fixed patterns, as opposed to regexes below
## [1] TRUE TRUE FALSE FALSE
Important
The order of arguments is like grepl(needle, haystack)
,
not vice versa. Also, this function is not vectorised with respect
to the first argument.
How the calls to grep(y, x, value=FALSE)
and grep(y, x, value=TRUE)
can be implemented based on
grepl and other operations we are already familiar with?
Note
(*) As a curiosity, agrepl performs approximate matching, which can account for a smöll nmber of tpyos.
agrepl("spam", x)
## [1] TRUE TRUE FALSE TRUE
agrepl("ham", x, ignore.case=TRUE)
## [1] TRUE TRUE TRUE TRUE
It is based on Levenshtein’s edit distance that measures the number of character insertions, deletions, or substitutions required to turn one string into another.
6.2.4. Using regular expressions (*)¶
Setting perl=TRUE
allows for identifying occurrences
of patterns specified by regular expressions (regexes).
grepl("^spam", x, perl=TRUE) # strings that begin with `spam`
## [1] TRUE FALSE FALSE FALSE
grepl("(?i)^spam|spam$", x, perl=TRUE) # begin or end; case ignored
## [1] TRUE TRUE TRUE FALSE
Note
For more details on regular expressions in general, see, e.g.,
[24]. The ultimate reference on the PCRE2
pattern syntax is the Unix man
page
pcre2pattern(3).
From now on, we assume that the reader is familiar with it.
Apart from the Perl-compatible regexes, R also gives access
to the TRE library (ERE-like), which is the default one; see help("regex")
.
However, we discourage its use because it is feature-poorer.
The list.files function generates the list of file names in a given directory that match a given regular expression. For instance, the following gives all CSV files in a folder:
list.files("~/Projects/teaching-data/r/", "\\.csv$")
## [1] "air_quality_1973.csv" "anscombe.csv" "iris.csv"
## [4] "titanic.csv" "tooth_growth.csv" "trees.csv"
## [7] "world_phones.csv"
Write a single regular expression that matches file names ending with
“.csv
” or “.csv.gz
”. Also, scribble a regex that matches CSV files
whose names do not begin with “eurusd
”.
6.2.5. Locating pattern occurrences¶
regexpr finds the first occurrence of a pattern in each string:
regexpr("spam", x, fixed=TRUE)
## [1] 1 3 -1 -1
## attr(,"match.length")
## [1] 4 4 -1 -1
## attr(,"index.type")
## [1] "chars"
## attr(,"useBytes")
## [1] TRUE
In particular, there is a pattern occurrence starting at the
third code point of the second string in x
.
Moreover, the last string has no pattern match, which is denoted by -1
.
The match.length
attribute is generally more informative when
searching with regular expressions.
To locate all the matches, i.e., globally, we use gregexpr:
# `spam` followed by 0 or more letters, case insensitively
gregexpr("(?i)spam\\p{L}*", x, perl=TRUE)
## [[1]]
## [1] 1
## attr(,"match.length")
## [1] 4
## attr(,"index.type")
## [1] "chars"
## attr(,"useBytes")
## [1] TRUE
##
## [[2]]
## [1] 3 12
## attr(,"match.length")
## [1] 8 4
## attr(,"index.type")
## [1] "chars"
## attr(,"useBytes")
## [1] TRUE
##
## [[3]]
## [1] 7
## attr(,"match.length")
## [1] 4
## attr(,"index.type")
## [1] "chars"
## attr(,"useBytes")
## [1] TRUE
##
## [[4]]
## [1] -1
## attr(,"match.length")
## [1] -1
## attr(,"index.type")
## [1] "chars"
## attr(,"useBytes")
## [1] TRUE
As we noted in Section 4.4.2, wrapping the results in a list was a clever choice for the number of matches can obviously vary between strings.
In Section 7.2, we will look at the Map function, which, along with substring introduced below, can aid in getting the most out of such data. Meanwhile, let’s just mention that regmatches extracts the matching substrings:
regmatches(x, gregexpr("(?i)spam\\p{L}*", x, perl=TRUE))
## [[1]]
## [1] "spam"
##
## [[2]]
## [1] "spammite" "spam"
##
## [[3]]
## [1] "SPAM"
##
## [[4]]
## character(0)
Note
(*) Consider what happens when a regular expression contains parenthesised subexpressions (capture groups).
r <- "(?<basename>[^. ]+)\\.(?<extension>[^ ]*)"
This regex consists of two capture groups separated by
a dot. The first one is labelled “basename
”. It comprises several
arbitrary characters except for spaces and dots.
The second group, named “extension
”, is a substring consisting
of anything but spaces.
Such a pattern can be used for unpacking space-delimited lists of file names.
z <- "dataset.csv.gz something_else.txt spam"
regexpr(r, z, perl=TRUE)
## [1] 1
## attr(,"match.length")
## [1] 14
## attr(,"index.type")
## [1] "chars"
## attr(,"useBytes")
## [1] TRUE
## attr(,"capture.start")
## basename extension
## [1,] 1 9
## attr(,"capture.length")
## basename extension
## [1,] 7 6
## attr(,"capture.names")
## [1] "basename" "extension"
gregexpr(r, z, perl=TRUE)
## [[1]]
## [1] 1 16
## attr(,"match.length")
## [1] 14 18
## attr(,"index.type")
## [1] "chars"
## attr(,"useBytes")
## [1] TRUE
## attr(,"capture.start")
## basename extension
## [1,] 1 9
## [2,] 16 31
## attr(,"capture.length")
## basename extension
## [1,] 7 6
## [2,] 14 3
## attr(,"capture.names")
## [1] "basename" "extension"
The capture.*
attributes give us access to the matches to the individual
capture groups, i.e., the basename
and the extension
.
(*) Check out the difference between the results generated by regexec and regexpr as well as between the outputs of gregexec and gregexpr.
6.2.6. Replacing pattern occurrences¶
sub and gsub can replace the first and all, respectively, matches to a pattern:
x <- c("spam", "y spammite spam", "yummy SPAM", "sram")
sub("spam", "ham", x, fixed=TRUE)
## [1] "ham" "y hammite spam" "yummy SPAM" "sram"
gsub("spam", "ham", x, fixed=TRUE)
## [1] "ham" "y hammite ham" "yummy SPAM" "sram"
Note
(*) If a regex defines capture groups, matches thereto can be mentioned not only in the pattern itself but also in the replacement string:
gsub("(\\p{L})\\p{L}\\1", "\\1", "aha egg gag NaN spam", perl=TRUE)
## [1] "a egg g N spam"
Matched are, in the following order: a letter (it is a capture group), another letter, and the former letter again. Each such palindrome of length three is replaced with just the repeated letter.
(*) Display the source code of glob2rx by calling
print(glob2rx)
and study how this function converts wildcards such as file???.*
or *.csv
to regular expressions that can be passed to, e.g.,
list.files.
6.2.7. Splitting strings into tokens¶
strsplit divides each string in a character vector into chunks.
strsplit(c("spam;spam;eggs;;bacon", "spam"), ";", fixed=TRUE)
## [[1]]
## [1] "spam" "spam" "eggs" "" "bacon"
##
## [[2]]
## [1] "spam"
Note that this time the search pattern specifying the token delimiter is given as the second argument (an inconsistency).
6.3. Other string operations¶
6.3.1. Extracting substrings¶
substring extracts parts of strings between given character position ranges.
substring("spammity spam", 1, 4) # from the first to the fourth character
## [1] "spam"
substring("spammity spam", 10) # from the tenth to end
## [1] "spam"
substring("spammity spam", c(1, 10), c(4, 14)) # vectorisation
## [1] "spam" "spam"
substring(c("spammity spam", "bacon and eggs"), 1, c(4, 5))
## [1] "spam" "bacon"
Note
There is also a replacement (compare Section 9.3.6) version of the foregoing:
x <- "spam, spam, bacon, and spam"
substring(x, 7, 11) <- "eggs"
print(x)
## [1] "spam, eggs, bacon, and spam"
Unfortunately, the number of characters in the replacement string
should not exceed the length of the part being substituted
(try "chickpeas"
instead of "eggs"
).
However, substring replacement can be written as
a composition of substring extraction and concatenation:
paste(substring(x, 1, 6), "chickpeas", substring(x, 11), sep="")
## [1] "spam, chickpeas, bacon, and spam"
Take the output generated by regexpr and apply substring
to extract the pattern occurrences. If there is no match in a string,
the corresponding output should be NA
.
6.3.2. Translating characters¶
tolower and toupper converts between lower and upper case:
toupper("spam")
## [1] "SPAM"
Note
Like many other string operations in base R, these functions perform very simple character substitutions. They might not be valid in natural language processing tasks. For instance, groß is not converted to GROSS, being the correct case folding in German.
Moreover, chartr translates individual characters:
chartr("\\", "/", "c:\\windows\\system\\cmd.exe") # chartr(old, new, x)
## [1] "c:/windows/system/cmd.exe"
chartr("([S", ")]*", ":( :S :[")
## [1] ":) :* :]"
In the first line, we replace each backslash with a slash.
The second example replaces “(
”, “[
”, and “S
” with
“)
”, “]
”, and “*
”, respectively.
6.3.3. Ordering strings¶
We have previously mentioned that operators and functions such as `<`, `>=`, sort, order, rank, and xtfrm[4] are based on the lexicographic ordering of strings.
sort(c("chłodny", "hardy", "chladný", "hladný"))
## [1] "chladný" "chłodny" "hardy" "hladný"
It is worth noting that the ordering depends on the currently
selected locale; see Sys.getlocale("LC_COLLATE")
.
For instance, in the Slovak language setting, we would obtain
"hardy"
< "hladný"
< "chladný"
< "chłodny"
.
Note
Many “structured” data items can be displayed or transmitted
as human-readable strings. In particular, we know that
as.numeric can convert a string to a number.
Moreover, Section 10.3.1 will discuss
date-time objects such as "1970-01-01 00:00:00 GMT"
.
We will be processing them with specialised functions such as
strptime and strftime.
Important
(*) Many string operations in base R are not necessarily portable. The stringx package defines drop-in, “fixed” replacements therefor. They are based on the International Components for Unicode (ICU) library, a de facto standard for processing Unicode text, and the R package stringi; see [26].
# call install.packages("stringx") first
suppressPackageStartupMessages(library("stringx")) # load the package
sort(c("chłodny", "hardy", "chladný", "hladný"), locale="sk_SK")
## [1] "hardy" "hladný" "chladný" "chłodny"
toupper("gro\u00DF") # compare base::toupper("gro\u00DF")
## [1] "GROSS"
detach("package:stringx") # remove the package from the search path
6.4. Other atomic vector types (*)¶
We have discussed four vector types: logical
, double
, character
,
and list
. To get a more complete picture of the sequence-like types in R,
let’s briefly mention integer
, complex
, and raw
atomic types
so that we are not surprised when we encounter them.
6.4.1. Integer vectors (*)¶
Integer scalars can be input manually by using the
L
suffix:
(x <- c(1L, 2L, -1L, NA_integer_)) # looks like numeric
## [1] 1 2 -1 NA
typeof(x) # but is integer
## [1] "integer"
Some functions return them in a few contexts[5]:
typeof(1:10) # seq(1, 10) as well, but not seq(1, 10, 1)
## [1] "integer"
as.integer(c(-1.1, 0, 1.9, 2.1)) # truncate/round towards 0
## [1] -1 0 1 2
In most expressions, integer vectors behave like numeric ones.
They are silently coerced to double
if need be. Usually, there is no
practical[6] reason to distinguish between them.
For example:
1L/2L # like 1/2 == 1.0/2.0
## [1] 0.5
Note
(*)
R integer
s are 32-bit signed types. In the double
type, we can store
more of them. The maximal contiguously representable integer
is \(2^{31}-1\) and \(2^{53}\), respectively; see Section 3.2.3:
as.integer(2^31-1) + 1L # 32-bit integer overflow
## Warning in as.integer(2^31 - 1) + 1L: NAs produced by integer overflow
## [1] NA
as.integer(2^31-1) + 1 == 2^31 # integer+double == double – OK
## [1] TRUE
(2^53 - 1) + 1 == 2^53 # OK
## [1] TRUE
(2^53 + 1) - 1 == 2^53 # lost due to FP rounding; left side equals 2^53 - 1
## [1] FALSE
Note
Since R 3.0, there is support for vectors longer
than \(2^{31}-1\) elements. As there are no 64-bit integers
in R, long vectors are indexed by double
s (we have been doing
all this time). In particular, x[1.9]
is the same as x[1]
,
and x[-1.9]
means x[-1]
, i.e., the fractional part is truncated.
It is why the notation like x[
length(x)*0.2]
works, whether the length of x
is a multiple of five or not.
6.4.2. Raw vectors (*)¶
Vectors of the type raw
can store bytes, i.e., unsigned
8-bit integers, whose range is 0–255.
For example:
as.raw(c(-1, 0, 1, 2, 0xc0, 254, 255, 256, NA))
## Warning: out-of-range values treated as 0 in coercion to raw
## [1] 00 00 01 02 c0 fe ff 00 00
They are displayed as two-digit hexadecimal (base-16) numbers.
There are no raw NA
s.
Only a few functions deal with such vectors: e.g., readBin, charToRaw, and rawToChar.
Interestingly, the meaning of the logical operators differs for raw vectors; they denote bitwise operations. See also bitwAnd, bitwOr etc. that work on integer vectors.
xor(as.raw(0xf0), as.raw(0x0f))
## [1] ff
bitwXor(0x0fff0f00, 0x0f00f0ff)
## [1] 16777215
(*) One use case of bitwise operations is for representing a selection of items in a small set of possible values. This can be useful for communicating with routines implemented in C/C++. For instance, let’s define three flags:
HAS_SPAM <- 0x01 # binary 00000001
HAS_BACON <- 0x02 # binary 00000010
HAS_EGGS <- 0x04 # binary 00000100
Now a particular subset can be created using the bitwise OR:
dish <- bitwOr(HAS_SPAM, HAS_EGGS) # {spam, eggs}
Testing for inclusion is done via the bitwise AND:
as.logical(bitwAnd(dish, c(HAS_SPAM, HAS_BACON, HAS_EGGS)))
## [1] TRUE FALSE TRUE
6.4.3. Complex vectors (*)¶
We can also play with vectors of the type complex
,
with “1i
” representing the imaginary unit, \(\sqrt{-1}\).
Complex numbers appear in quite a few engineering or scientific
applications, e.g., in physics, electronics, or signal processing.
They are (at least: ought to be) part of introductory
subjects or textbooks in university-level mathematics,
including the statistics- and machine learning-orientated ones
because of their heavy use of numerical computing; see e.g.,
[19, 30].
c(0, 1i, pi+pi*1i, NA_complex_)
## [1] 0.0000+0.0000i 0.0000+1.0000i 3.1416+3.1416i NA
Apart from the basic operators, mathematical and aggregation functions,
procedures like fft, solve, qr,
or svd can be fed with or produce such data. For more details,
see help("complex")
and some matrix examples in Chapter 11.
6.5. Exercises¶
Exercises marked with (*) might require tinkering with regular expressions or third-party R packages.
Answer the following questions.
How many characters are there in the string
"ab\n\\\t\\\\\""
? What aboutr"-{ab\n\\\t\\\\\"-)}-"
?What is the result of a call to paste
(NA, 1:5, collapse="")
?What is the meaning of the following sprintf format strings: “
%s
”, “%20s
”, “%-20s
”, “%f
”, “%g
”, “%e
”, “%5f
”, “%5.2f%%
”, “%.2f
”, “%0+5f
”, and “[%+-5.2f]
”?What is the difference between regexpr and gregexpr? What does “g” in the latter function name stand for?
What is the result of a call to grepl
(
c("spam", "spammity spam", "aubergines"), "spam")
?Is it always the case that “
"Aaron" < "Zorro"
”?Why “
x < "10"
” and “x < 10
” may return different results?If
x
is a character vector, is “x == x
” always equal toTRUE
?If
x
andy
are character vectors of lengths \(n\) and \(m\), respectively, what is the length of the output of match(x, y)
?If
x
is a named vector, why is there a difference betweenx[NA]
andx[NA_character_]
?What is the difference between “
x == y
” and “x %in% y
”?
Let x
, y
, and z
be atomic vectors and a
and b
be single strings.
Generate the same results as
pastena(x, collapse=b)
,
pastena(x, y, sep=a)
,
pastena(x, y, sep=a, collapse=b)
,
pastena(x, y, z, sep=a)
,
pastena(x, y, z, sep=a, collapse=b)
,
assuming that pastena is a version of paste
(which we do not have) that handles missing data in a way consistent
with most other functions.
Based on list.files and glob2rx, generate the list of all PDFs on your computer. Then, use file.size to filter out the files smaller than 10 MiB.
Read a text file that stores a long paragraph of some banal prose. Concatenate all the lines to form a single, long string. Using strwrap and cat, output the paragraph on the console, nicely formatted to fit a block of text of an aesthetic width, say, 60 columns.
(*) Implement a simplified version of basename and dirname.
(*) Implement an operation similar to trimws using the functions introduced in this chapter.
(*) Write a regex that extracts all words from each string in a given character vector.
(*) Write a regex that extracts, from each string in a character vector, all:
integers numbers (signed or unsigned),
floating-point numbers,
numbers of any kind (including those in scientific notation),
#hashtags,
email@address.es,
hyperlinks of the form http://… and https://….
(*) What do 42i
, 42L
, and 0x42
stand for?
(*) Check out stri_sort in the stringi package
(or sort.character in stringx) for a way
to obtain an ordering like "a1"
< "a2"
< "a10"
< "a11"
< "a100"
.
(*)
In sprintf,
the formatter "%20s"
means that if a string is less than 20 bytes
long, the remaining bytes will be replaced with spaces.
Only for ASCII characters (English letters, digits, some punctuation marks,
etc.), it is true that one character is represented by one byte.
Other Unicode code points can take up between two and four bytes.
cat(sprintf("..%6s..", c("abc", "1!<", "aßc", "ąß©")), sep="\n") # aligned?
## .. abc..
## .. 1!<..
## .. aßc..
## ..ąß©..
Use the stri_pad function from the stringi package to align the strings aesthetically. Alternatively, check out sprintf from stringx.
(*) Implement an operation similar to stri_pad from stringi using the functions introduced in this chapter.