Chapter 7. Strings and Factors

As well as dealing with numbers and logical values, at some point you will almost certainly need to manipulate text. This is particularly common when you are retrieving or cleaning datasets. Perhaps you are trying to turn the text of a log file into meaningful values, or correct the typos in your data. These data-cleaning activities will be discussed in more depth in Chapter 13, but for now, you will learn how to manipulate character vectors.

Factors are used to store categorical data like gender (“male” or “female”) where there are a limited number of options for a string. They sometimes behave like character vectors and sometimes like integer vectors, depending upon context.

Chapter Goals

After reading this chapter, you should:

  • Be able to construct new strings from existing strings
  • Be able to format how numbers are printed
  • Understand special characters like tab and newline
  • Be able to create and manipulate factors

Strings

Text data is stored in character vectors (or, less commonly, character arrays). It’s important to remember that each element of a character vector is a whole string, rather than just an individual character. In R, “string” is an informal term that is used because “element of a character vector” is quite a mouthful.

The fact that the basic unit of text is a character vector means that most string manipulation functions operate on vectors of strings, in the same way that mathematical operations are vectorized.

Constructing and Printing Strings

As you’ve already seen, character vectors can be created with the c function. We can use single or double quotes around our strings, as long as they match, though double quotes are considered more standard:

c(
  "You should use double quotes most of the time",
  'Single quotes are better for including " inside the string'
)
## [1] "You should use double quotes most of the time"
## [2] "Single quotes are better for including " inside the string"

The paste function combines strings together. Each vector passed to it has its elements recycled to reach the length of the longest input, and then the strings are concatenated, with a space separating them. We can change the separator by passing an argument called sep, or use the related function paste0 to have no separator. After all the strings are combined, the result can be collapsed into one string containing everything using the collapse argument:

paste(c("red", "yellow"), "lorry")
## [1] "red lorry"    "yellow lorry"
paste(c("red", "yellow"), "lorry", sep = "-")
## [1] "red-lorry"    "yellow-lorry"
paste(c("red", "yellow"), "lorry", collapse = ", ")
## [1] "red lorry, yellow lorry"
paste0(c("red", "yellow"), "lorry")
## [1] "redlorry"    "yellowlorry"

The function toString is a variation of paste that is useful for printing vectors. It separates each element with a comma and a space, and can limit how much we print. In the following example, width = 40 limits the output to 40 characters:

x <- (1:15) ^ 2
toString(x)
## [1] "1, 4, 9, 16, 25, 36, 49, 64, 81, 100, 121, 144, 169, 196, 225"
toString(x, width = 40)
## [1] "1, 4, 9, 16, 25, 36, 49, 64, 81, 100...."

cat is a low-level function that works similarly to paste, but with less formatting. You should rarely need to call it directly yourself, but it is worth being aware of, since it is the basis for most of the print functions. cat can also accept a file argument[21] to write its output to a file:

cat(c("red", "yellow"), "lorry")
## red yellow lorry

Usually, when strings are printed to the console they are shown wrapped in double quotes. By wrapping a variable in a call to the noquote function, we can suppress those quotes. This can make the text more readable in some instances:

x <- c(
  "I", "saw", "a", "saw", "that", "could", "out",
  "saw", "any", "other", "saw", "I", "ever", "saw"
)
y <- noquote(x)
x
##  [1] "I"     "saw"   "a"     "saw"   "that"  "could" "out"   "saw"
##  [9] "any"   "other" "saw"   "I"     "ever"  "saw"
y
##  [1] I     saw   a     saw   that  could out   saw   any   other saw
## [12] I     ever  saw

Formatting Numbers

There are several functions for formatting numbers. formatC uses C-style formatting specifications that allow you to specify fixed or scientific formatting, the number of decimal places, and the width of the output. Whatever the options, the input should be one of the numeric types (including arrays), and the output is a character vector or array:

pow <- 1:3
(powers_of_e <- exp(pow))
## [1]  2.718  7.389 20.086
formatC(powers_of_e)
## [1] "2.718" "7.389" "20.09"
formatC(powers_of_e, digits = 3)               #3 sig figs
## [1] "2.72" "7.39" "20.1"
formatC(powers_of_e, digits = 3, width = 10)   #preceding spaces
## [1] "      2.72" "      7.39" "      20.1"
formatC(powers_of_e, digits = 3, format = "e") #scientific formatting
## [1] "2.718e+00" "7.389e+00" "2.009e+01"
formatC(powers_of_e, digits = 3, flag = "+")   #precede +ve values with +
## [1] "+2.72" "+7.39" "+20.1"

R also provides slightly more general C-style formatting with the function sprintf. This works in the same way as sprintf in every other language: the first argument contains placeholders for string or number variables, and further arguments are substituted into those placeholders. Just remember that most numbers in R are floating-point values rather than integers.

The first argument to sprintf specifies a formatting string, with placeholders for other values. For example, %s denotes another string, %f and %e denote a floating-point number in fixed or scientific format, respectively, and %d represents an integer. Additional arguments specify the values to replace the placeholders. As with the paste function, shorter inputs are recycled to match the longest input:

sprintf("%s %d = %f", "Euler's constant to the power", pow, powers_of_e)
## [1] "Euler's constant to the power 1 = 2.718282"
## [2] "Euler's constant to the power 2 = 7.389056"
## [3] "Euler's constant to the power 3 = 20.085537"
sprintf("To three decimal places, e ^ %d = %.3f", pow, powers_of_e)
## [1] "To three decimal places, e ^ 1 = 2.718"
## [2] "To three decimal places, e ^ 2 = 7.389"
## [3] "To three decimal places, e ^ 3 = 20.086"
sprintf("In scientific notation, e ^ %d = %e", pow, powers_of_e)
## [1] "In scientific notation, e ^ 1 = 2.718282e+00"
## [2] "In scientific notation, e ^ 2 = 7.389056e+00"
## [3] "In scientific notation, e ^ 3 = 2.008554e+01"

Alternative syntaxes for formatting numbers are provided with the format and prettyNum functions. format just provides a slightly different syntax for formatting strings, and has similar usage to formatC. prettyNum, on the other hand, is best for pretty formatting of very big or very small numbers:

format(powers_of_e)
## [1] " 2.718" " 7.389" "20.086"
format(powers_of_e, digits = 3)                    #at least 3 sig figs
## [1] " 2.72" " 7.39" "20.09"
format(powers_of_e, digits = 3, trim = TRUE)       #remove leading zeros
## [1] "2.72"  "7.39"  "20.09"
format(powers_of_e, digits = 3, scientific = TRUE)
## [1] "2.72e+00" "7.39e+00" "2.01e+01"
prettyNum(
  c(1e10, 1e-20),
  big.mark   = ",",
  small.mark = " ",
  preserve.width = "individual",
  scientific = FALSE
)
## [1] "10,000,000,000"            "0.00000 00000 00000 00001"

Special Characters

There are some special characters that can be included in strings. For example, we can insert a tab character using . In the following example, we use cat rather than print, since print performs an extra conversion to turn from a tab character into a backslash and a “t.” The argument fill = TRUE makes cat move the cursor to a new line after it is finished:

cat("foo	bar", fill = TRUE)
## foo  bar

Moving the cursor to a new line is done by printing a newline character, (this is true on all platforms; don’t try to use or for printing newlines to the R command line, since will just move the cursor to the start of the current line and overwrite what you have written):

cat("foo
bar", fill = TRUE)
## foo
## bar

Null characters, , are used to terminate strings in R’s internal code. It is an error to explicitly try to include them in a string (older versions of R will discard the contents of the string after the null character):

cat("foobar", fill = TRUE)  #this throws an error

Backslash characters need to be doubled up so that they aren’t mistaken for a special character. In this next example, the two input backslashes make just one backslash in the resultant string:

cat("foo\bar", fill = TRUE)
## fooar

If we are using double quotes around our string, then double quote characters need to be preceded by a backslash to escape them. Similarly, if we use single quotes around our strings, then single quote characters need to be escaped:

cat("foo"bar", fill = TRUE)
## foo"bar
cat('foo'bar', fill = TRUE)
## foo'bar

In the converse case, including single quotes in double-quoted strings or double quotes inside single-quoted strings, we don’t need an escape backslash:

cat("foo'bar", fill = TRUE)
## foo'bar
cat('foo"bar', fill = TRUE)
## foo"bar

We can make our computer beep by printing an alarm character, a, though the function alarm will do this in a more readable way. This can be useful to add to the end of a long analysis to notify you that it’s finished (as long as you aren’t in an open-plan office):

cat("a")
alarm()

Changing Case

Strings can be converted to contain all uppercase or all lowercase values using the functions toupper and tolower:

toupper("I'm Shouting")
## [1] "I'M SHOUTING"
tolower("I'm Whispering")
## [1] "i'm whispering"

Extracting Substrings

There are two related functions for extracting substrings: substring and substr. In most cases, it doesn’t matter which you use, but they behave in slightly different ways when you pass vectors of different lengths as arguments. For substring, the output is as long as the longest input; for substr, the output is as long as the first input:

woodchuck <- c(
  "How much wood would a woodchuck chuck",
  "If a woodchuck could chuck wood?",
  "He would chuck, he would, as much as he could",
  "And chuck as much wood as a woodchuck would",
  "If a woodchuck could chuck wood."
)
substring(woodchuck, 1:6, 10)

## [1] "How much w" "f a woodc"  " would c"   " chuck "    " woodc"
## [6] "uch w"
substr(woodchuck, 1:6, 10)
## [1] "How much w" "f a woodc"  " would c"   " chuck "    " woodc"

Splitting Strings

The paste function and its friends combine strings together. strsplit does the opposite, breaking them apart at specified cut points. We can break the woodchuck tongue twister up into words by splitting it on the spaces. In the next example, fixed = TRUE means that the split argument is a fixed string rather than a regular expression:

strsplit(woodchuck, " ", fixed = TRUE)
## [[1]]
## [1] "How"       "much"      "wood"      "would"     "a"         "woodchuck"
## [7] "chuck"
##
## [[2]]
## [1] "If"        "a"         "woodchuck" "could"     "chuck"     "wood?"
##
## [[3]]
##  [1] "He"     "would"  "chuck," "he"     "would," "as"     "much"
##  [8] "as"     "he"     "could"
##
## [[4]]
## [1] "And"       "chuck"     "as"        "much"      "wood"      "as"
## [7] "a"         "woodchuck" "would"
##
## [[5]]
## [1] "If"        "a"         "woodchuck" "could"     "chuck"     "wood."

Notice that strsplit returns a list (not a character vector or matrix). This is because its result consists of character vectors of possibly different lengths. When you only pass a single string as an input, this fact is easy to overlook. Be careful!

In our example, the trailing commas on some words are a little annoying. It would be better to split on an optional comma followed by a space. This is easily specified using a regular expression. ? means “make the previous character optional”:

strsplit(woodchuck, ",? ")
## [[1]]
## [1] "How"       "much"      "wood"      "would"     "a"         "woodchuck"
## [7] "chuck"
##
## [[2]]
## [1] "If"        "a"         "woodchuck" "could"     "chuck"     "wood?"
##
## [[3]]
##  [1] "He"    "would" "chuck" "he"    "would" "as"    "much"  "as"
##  [9] "he"    "could"
##
## [[4]]
## [1] "And"       "chuck"     "as"        "much"      "wood"      "as"
## [7] "a"         "woodchuck" "would"
##
## [[5]]
## [1] "If"        "a"         "woodchuck" "could"     "chuck"     "wood."

File Paths

R has a working directory, which is the default place that files will be read from or written to. You can see its location with getwd and change it with setwd:

getwd()
## [1] "d:/workspace/LearningR"
setwd("c:/windows")
getwd()
## [1] "c:/windows"

Notice that the directory components of each path are separated by forward slashes, even though they are Windows pathnames. For portability, in R you can always specify paths with forward slashes, and the file handling functions will magically replace them with backslashes if the operating system needs them.

You can also specify a double backslash to denote Windows paths, but forward slashes are preferred:

"c:\windows"           #remember to double up the slashes
"\\myserver\mydir"   #UNC names need four slashes at the start

Alternatively, you can construct file paths from individual directory names using file.path. This automatically puts forward slashes between directory names. It’s like a simpler, faster version of paste for paths:

file.path("c:", "Program Files", "R", "R-devel")
## [1] "c:/Program Files/R/R-devel"
R.home()      #same place: a shortcut to the R installation dir
## [1] "C:/PROGRA~1/R/R-devel"

Paths can be absolute (starting from a drive name or network share), or relative to the current working directory. In the latter case, . can be used for the current directory and .. can be used for the parent directory. ~ is shorthand for your user home directory. path.expand converts relative paths to absolute paths:

path.expand(".")
## [1] "."
path.expand("..")
## [1] ".."
path.expand("~")
## [1] "C:\Users\richie\Documents"

basename returns the name of a file without the preceding directory location. Conversely, dirname returns the name of the directory that a file is in:

file_name <- "C:/Program Files/R/R-devel/bin/x64/RGui.exe"
basename(file_name)
## [1] "RGui.exe"
dirname(file_name)
## [1] "C:/Program Files/R/R-devel/bin/x64"

Factors

Factors are a special variable type for storing categorical variables. They sometimes behave like strings, and sometimes like integers.

Creating Factors

Whenever you create a data frame with a column of text data, R will assume by default that the text is categorical data and perform some conversion. The following example dataset contains the heights of 10 random adults:

(heights <- data.frame(
  height_cm = c(153, 181, 150, 172, 165, 149, 174, 169, 198, 163),
  gender = c(
    "female", "male", "female", "male", "male",
    "female", "female", "male", "male", "female"
  )
))
##    height_cm gender
## 1        153 female
## 2        181   male
## 3        150 female
## 4        172   male
## 5        165   male
## 6        149 female
## 7        174 female
## 8        169   male
## 9        198   male
## 10       163 female

By inspecting the class of the gender column we can see that it is not, as you may have expected, a character vector, but is in fact a factor:

class(heights$gender)
## [1] "factor"

Printing the column reveals a little more about the nature of this factor:

heights$gender
##  [1] female male   female male   male   female female male   male   female
## Levels: female male

Each value in the factor is a string that is constrained to be either “female,” “male,” or missing. This constraint becomes obvious if we try to add a different string to the genders:

heights$gender[1] <- "Female"  #notice the capital "F"
## Warning: invalid factor level, NA generated
heights$gender
##  [1] <NA>   male   female male   male   female female male   male   female
## Levels: female male

The choices “female” and “male” are called the levels of the factor and can be retrieved with the levels function:

levels(heights$gender)
## [1] "female" "male"

The number of these levels (equivalent to the length of the levels of the factor) can be retrieved with the nlevels function:

nlevels(heights$gender)
## [1] 2

Outside of their automatic creation inside data frames, you can create factors using the factor function. The first (and only compulsory) argument is a character vector:

gender_char <- c(
  "female", "male", "female", "male", "male",
  "female", "female", "male", "male", "female"
)
(gender_fac <- factor(gender_char))
##  [1] female male   female male   male   female female male   male   female
## Levels: female male

Changing Factor Levels

We can change the order of the levels when the factor is created by specifying a levels argument:

factor(gender_char, levels = c("male", "female"))
##  [1] female male   female male   male   female female male   male   female
## Levels: male female

If we want to change the order of the factor levels after creation, we again use the factor function, this time passing in the existing factor (rather than a character vector):

factor(gender_fac, levels = c("male", "female"))
##  [1] female male   female male   male   female female male   male   female
## Levels: male female

Warning

What we shouldn’t do is directly change the levels using the levels function. This will relabel each level, changing data values, which is usually undesirable.

In the next example, directly setting the levels of the factor changes male data to female data, and female data to male data, which isn’t what we want:

levels(gender_fac) <- c("male", "female")
gender_fac
##  [1] male   female male   female female male   male   female female male
## Levels: male female

The relevel function is an alternative way of changing the order of factor levels. In this case, it just lets you specify which level comes first. As you might imagine, the use case for this function is rather niche—it can come in handy for regression models where you want to compare different categories to a reference category. Most of the time you will be better off calling factor if you want to set the levels:

relevel(gender_fac, "male")
##  [1] male   female male   female female male   male   female female male
## Levels: male female

Dropping Factor Levels

In the process of cleaning datasets, you may end up removing all the data corresponding to a factor level. Consider this dataset of times to travel into work using different modes of transport:

getting_to_work <- data.frame(
  mode = c(
    "bike", "car", "bus", "car", "walk",
    "bike", "car", "bike", "car", "car"
),
  time_mins = c(25, 13, NA, 22, 65, 28, 15, 24, NA, 14)
)

Not all the times have been recorded, so our first task is to remove the rows where time_mins is NA:

(getting_to_work <- subset(getting_to_work, !is.na(time_mins)))
##    mode time_mins
## 1  bike        25
## 2   car        13
## 4   car        22
## 5  walk        65
## 6  bike        28
## 7   car        15
## 8  bike        24
## 10  car        14

Looking at the mode column, there are now just three different values, but we still have the same four levels in the factor. We can see this with the unique function (the levels function will, of course, also tell us the levels of the factor):

unique(getting_to_work$mode)
## [1] bike car  walk
## Levels: bike bus car walk

If we want to drop the unused levels of the factor, we can use the droplevels function. This accepts either a factor or a data frame. In the latter case, it drops the unused levels in all the factors of the input. Since there is only one factor in our example data frame, the two lines of code in the next example are equivalent:

getting_to_work$mode <- droplevels(getting_to_work$mode)
getting_to_work <- droplevels(getting_to_work)
levels(getting_to_work$mode)
## [1] "bike" "car"  "walk"

Ordered Factors

Some factors have levels that are semantically greater than or less than other levels. This is common with multiple-choice survey questions. For example, the survey question “How happy are you?” could have the possible responses “depressed,” “grumpy,” “so-so,” “cheery,” and ‘`ecstatic.’'[22] The resulting variable is categorical, so we can create a factor with the five choices as levels. Here we generate ten thousand random responses using the sample function:

happy_choices <- c("depressed", "grumpy", "so-so", "cheery", "ecstatic")
happy_values <- sample(
  happy_choices,
  10000,
  replace = TRUE
)
happy_fac <- factor(happy_values, happy_choices)
head(happy_fac)
## [1] grumpy    depressed cheery    ecstatic  grumpy    grumpy
## Levels: depressed grumpy so-so cheery ecstatic

In this case, the five choices have a natural ordering to them: “grumpy” is happier than “depressed,” “so-so” is happier than “grumpy,” and so on. This means that it is better to store the responses in an ordered factor. We can do this using the ordered function (or by passing the argument ordered = TRUE to factor):

happy_ord <- ordered(happy_values, happy_choices)
head(happy_ord)
## [1] grumpy    depressed cheery    ecstatic  grumpy    grumpy
## Levels: depressed < grumpy < so-so < cheery < ecstatic

An ordered factor is a factor, but a normal factor isn’t ordered:

is.factor(happy_ord)
## [1] TRUE
is.ordered(happy_fac)
## [1] FALSE

For most purposes, you don’t need to worry about using ordered factors—you will only see a difference in some models—but they can be useful for analyzing survey data.

Converting Continuous Variables to Categorical

A useful way of summarizing a numeric variable is to count how many values fall into different “bins.” The cut function cuts a numeric variable into pieces, returning a factor. It is commonly used with the table function to get counts of numbers in different groups. (The hist function, which draws histograms, provides an alternative way of doing this, as does count in the plyr package, which we will see later.)

In the next example, we randomly generate the ages of ten thousand workers (from 16 to 66, using a beta distribution) and put them in 10-year-wide groups:

ages <- 16 + 50 * rbeta(10000, 2, 3)
grouped_ages <- cut(ages, seq.int(16, 66, 10))
head(grouped_ages)
## [1] (26,36] (16,26] (26,36] (26,36] (26,36] (46,56]
## Levels: (16,26] (26,36] (36,46] (46,56] (56,66]
table(grouped_ages)
## grouped_ages
## (16,26] (26,36] (36,46] (46,56] (56,66]
##    1844    3339    3017    1533     267

In this case, the bulk of the workforce falls into the 26-to-36 and 36-to-46 categories (as a direct consequence of the shape of our beta distribution).

Notice that ages is a numeric variable and grouped_ages is a factor:

class(ages)
## [1] "numeric"
class(grouped_ages)
## [1] "factor"

Converting Categorical Variables to Continuous

The converse case of converting a factor into a numeric variable is most useful during data cleaning. If you have dirty data where numbers are mistyped, R may interpret them as strings and convert them to factors during the import process. In this next example, one of the numbers has a double decimal place. Import functions such as read.table, which we will look at in Chapter 12, would fail to parse such a string into numeric format, and default to making the column a character vector:

dirty <- data.frame(
  x = c("1.23", "4..56", "7.89")
)

To convert the x column to be numeric, the obvious solution is to call as.numeric. Unfortunately, it gives the wrong answer:

as.numeric(dirty$x)
## [1] 1 2 3

Calling as.numeric on a factor reveals the underlying integer codes that the factor uses to store its data. In general, a factor f can be reconstructed from levels(f)[as.integer(f)].

To correctly convert the factor to numeric, we can first retrieve the values by converting the factor to a character vector. The second value is NA because 4..56 is not a genuine number:

as.numeric(as.character(dirty$x))
## Warning: NAs introduced by coercion
## [1] 1.23   NA 7.89

This is slightly inefficient, since repeated values have to be converted multiple times. As the FAQ on R notes, it is better to convert the factor’s levels to be numeric, then reconstruct the factor as above:

as.numeric(levels(dirty$x))[as.integer(dirty$x)]
## Warning: NAs introduced by coercion
## [1] 1.23   NA 7.89

Since this is not entirely intuitive, if you want to do this task regularly, you can wrap it into a function for convenience:

factor_to_numeric <- function(f)
{
  as.numeric(levels(f))[as.integer(f)]
}

Generating Factor Levels

For balanced data, where there are an equal number of data points for each level, the gl function can be used to generate a factor. In its simplest form, the function takes an integer for the number of levels in the resultant factor, and another integer for how many times each level should be repeated. More commonly, you will want to set the names of the levels, which is achieved by passing a character vector to the labels argument. More complex level orderings, such as alternating values, can be created by also passing a length argument:

gl(3, 2)
## [1] 1 1 2 2 3 3
## Levels: 1 2 3
gl(3, 2, labels = c("placebo", "drug A", "drug B"))
## [1] placebo placebo drug A  drug A  drug B  drug B
## Levels: placebo drug A drug B
gl(3, 1, 6, labels = c("placebo", "drug A", "drug B")) #alternating
## [1] placebo drug A  drug B  placebo drug A  drug B
## Levels: placebo drug A drug B

Combining Factors

Where we have multiple categorical variables, it is sometimes useful to combine them into a single factor, where each level consists of the interactions of the individual variables:

treatment <- gl(3, 2, labels = c("placebo", "drug A", "drug B"))
gender <- gl(2, 1, 6, labels = c("female", "male"))
interaction(treatment, gender)
## [1] placebo.female placebo.male   drug A.female  drug A.male
## [5] drug B.female  drug B.male
## 6 Levels: placebo.female drug A.female drug B.female ... drug B.male

Summary

  • You can combine strings together using paste and its derivatives.
  • There are many functions for formatting numbers.
  • Categorical data is stored in factors (or ordered factors).
  • Each possible category in a factor is called a level.
  • Continuous variables can be cut into categorical variables.

Test Your Knowledge: Quiz

Question 7-1
Name as many functions as you can think of for formatting numbers.
Question 7-2
How might you make your computer beep?
Question 7-3
What are the classes of the two types of categorical variable?
Question 7-4
What happens if you add a value to a factor that isn’t one of the levels?
Question 7-5
How do you convert a numeric variable to categorical?

Test Your Knowledge: Exercises

Exercise 7-1
Display the value of pi to 16 significant digits. [5]
Exercise 7-2

Split these strings into words, removing any commas or hyphens:

x <- c(
  "Swan swam over the pond, Swim swan swim!",
  "Swan swam back again - Well swum swan!"
)

[5]

Exercise 7-3

For your role-playing game, each of your adventurer’s character attributes is calculated as the sum of the scores from three six-sided dice rolls. To save arm-ache, you decide to use R to generate the scores. Here’s a helper function to generate them:

#n specifies the number of scores to generate.
#It should be a natural number.
three_d6 <- function(n)
{
  random_numbers <- matrix(
    sample(6, 3 * n, replace = TRUE),
    nrow = 3
  )
  colSums(random_numbers)
}

Big scores give characters bonuses, and small scores give characters penalties, according to the following table:

Score Bonus

3

–3

4, 5

–2

6 to 8

–1

9 to 12

0

13 to 15

+1

16, 17

+2

18

+3

Use the three_d6 function to generate 1,000 character attribute scores. Create a table of the number of scores with different levels of bonus. [15]



[21] Pedantically, it accepts a path to a file, or a connection to a file, as returned by the file function.

[22] This is sometimes called a Likert scale.

..................Content has been hidden....................

You can't read the all page of ebook, please click here login for view all page.
Reset