Historical Context & Motivation
The concept of a vector as a first-class data structure traces its lineage through decades of statistical computing language design. Unlike general-purpose languages such as C or Java, which treat arrays as contiguous memory blocks requiring explicit size declarations, R inherited a philosophy from its predecessors S and S-PLUS: every datum, even a single scalar, is fundamentally a vector. This design decision reflects the reality of statistical work, where operations on entire columns of data—means, standard deviations, regressions—are far more common than operations on individual numbers. Understanding how vectors are constructed is therefore not merely a syntactic exercise; it is the gateway to idiomatic, performant R programming.
c() function for concatenating values into vectors, establishing the syntax that R would later adopt almost verbatim.c(), the : operator, and seq() as the canonical trio for vector creation, a convention that has remained unchanged for over two decades.The central question this lesson addresses is straightforward yet foundational: how do you efficiently construct vectors of arbitrary or patterned data in R? Whether you are assembling observed data points by hand, generating index sequences for subsetting, or producing evenly spaced grids for numerical analysis, the three tools—c(), :, and seq()—cover virtually every use case you will encounter.
Core Principles & Definitions
Before examining syntax, it is essential to internalize several foundational principles that govern how R treats vectors. These principles explain not just how to create vectors but why certain behaviors—like automatic type coercion—occur during construction.
Atomic Homogeneity
logical, integer, double, character, or complex. Mixing types triggers implicit coercion following the hierarchy logical → integer → double → character.Scalars Are Length-1 Vectors
x <- 5 creates a numeric vector of length one. This uniformity means every vector creation tool works consistently regardless of the number of elements.c() Concatenates Recursively
c() function (short for "combine" or "concatenate") flattens nested calls, so c(1, c(2, 3)) yields [1] 1 2 3, not a nested structure.The Colon Operator Generates Integers
a:b generates a sequence from a to b incrementing (or decrementing) by 1. It has high operator precedence, which can produce surprising results in compound expressions if not parenthesized.seq() Provides Full Control
seq() function generalizes the colon operator by accepting from, to, by, and length.out parameters, enabling fractional steps and precise control over the number of generated elements.c() is the construction crew that installs them side by side. The colon operator and seq() are blueprints that tell the crew how many mailboxes to install and at what intervals to label them.Visual Explanation — How Vectors Are Built
The following diagram illustrates the three primary mechanisms for constructing vectors in R. On the left, c() combines arbitrary values into a single flat vector. In the center, the colon operator produces a unit-step integer sequence. On the right, seq() offers configurable start, end, step size, and output length parameters.
c() (left, arbitrary values), : (center, unit-step sequences), and seq() (right, configurable sequences). Note the index labels [1], [2], … beneath each cell, reflecting R's 1-based indexing.Each panel in the diagram represents a distinct use case. The c() function is the most general tool—it accepts any combination of values and existing vectors, flattening them into a single one-dimensional structure. The colon operator is a syntactic shorthand optimized for generating consecutive integer-like sequences; it is particularly useful for loop counters and index ranges. Finally, seq() subsumes the colon operator's functionality while adding the ability to specify non-unit step sizes and to control the exact number of elements produced—an indispensable feature when constructing evaluation grids for numerical methods.
How Vector Creation Works Internally
Under the hood, R allocates contiguous memory for vector storage, much like a C-style array. Understanding the semantics of each constructor clarifies edge cases—especially around type coercion and floating-point behavior—that can produce subtle bugs in production code.
c() — The Universal Concatenator
The function signature is c(..., recursive = FALSE). It accepts a variadic number of arguments and returns a single atomic vector. When elements of different types are mixed, R coerces all elements to the most general type in the hierarchy: logical < integer < double < character. For instance, c(TRUE, 3L, 2.5) coerces TRUE to 1L and then both integers to doubles, yielding c(1.0, 3.0, 2.5). Named elements can be specified inline: c(x = 1, y = 2) produces a named numeric vector.
c() always coerces to the rightmost type present among its arguments.The : Operator — Compact Integer Sequences
The expression a:b is syntactic sugar for seq.int(a, b). It generates a sequence starting at a, incrementing by +1 (or −1 if a > b), until it reaches or passes b. A critical caveat is operator precedence: the colon binds tighter than arithmetic operators, so 1:3 + 1 is parsed as (1:3) + 1, yielding 2 3 4 via vectorized addition, not 1:4. Always use parentheses when combining : with arithmetic.
a and b are both integers, the result is an integer vector. If either is a double (e.g., 1.5:4.5), the result is a double vector.seq() — The General Sequence Generator
The full signature is seq(from, to, by, length.out, along.with). You may specify either by (step size) or length.out (desired number of elements), but not both simultaneously—R computes the missing parameter from the relationship: length.out = (to − from) / by + 1. The along.with argument is a convenience that sets length.out = length(along.with), producing an index vector matching the length of an existing object. Two lightweight variants—seq_len(n) and seq_along(x)—are preferred in production code because they handle edge cases (such as zero-length input) more safely than 1:length(x).
length.out is specified instead of by, R computes by = (to − from) / (length.out − 1), guaranteeing that both endpoints are included.Detailed Breakdown — Variants and Edge Cases
Beyond the three primary constructors, R offers several related functions and patterns for generating vectors. Understanding these variants—and the edge cases that arise when inputs are unusual—distinguishes a proficient R programmer from a novice.
c(), :, and seq() based on the properties of the desired vector. The bottom banner highlights the defensive-programming variants seq_len() and seq_along().Edge Cases and Common Pitfalls
| Expression | Result | Explanation |
|---|---|---|
c() | NULL | No arguments produces NULL, not an empty vector. Use integer(0) for an empty typed vector. |
1:0 | 1 0 | The colon generates a descending sequence when from > to. This is valid but surprising when used as 1:length(x) and x has length 0. |
seq_len(0) | integer(0) | Safely returns a zero-length integer vector, avoiding the 1:0 trap. |
c(1, "two") | "1" "two" | The numeric 1 is silently coerced to the character "1". This is a common source of downstream type errors. |
seq(0, 1, by=0.3) | 0.0 0.3 0.6 0.9 | The endpoint 1.0 is excluded because 0.9 + 0.3 = 1.2 > 1.0. Use length.out if both endpoints must appear. |
for (i in seq_along(x)) instead of for (i in 1:length(x)). When x has length 0, 1:0 produces c(1, 0) and the loop body executes twice on invalid indices—a classic off-by-one bug.Worked Example — Building a Simulation Grid
Suppose you are preparing a Monte Carlo simulation where you need to evaluate a function at 100 evenly spaced points between 0 and 2π, and you also need a vector of the first 10 positive integers for loop indexing plus a manually specified vector of sample sizes.
c() is the appropriate tool.sample_sizes <- c(50, 100, 500, 1000, 5000)reps <- 1:10 → [1] 1 2 3 4 5 6 7 8 9 10theta <- seq(from = 0, to = 2 * pi, length.out = 100)length(), typeof(), and head() to inspect each vector and confirm correct construction. This is good practice before passing vectors into expensive computations.length(theta) # 100
typeof(theta) # "double"
head(theta, 4) # 0.000 0.0635 0.1269 0.1904c() flattens its arguments, this is straightforward.all_results <- c(results_run1, results_run2)Strengths, Limitations & When to Use Each
Each vector constructor occupies a distinct niche in terms of expressiveness, performance, and safety. The following comparison table summarizes their characteristics to aid in choosing the right tool for a given task.
| Feature | c() | : (colon) | seq() |
|---|---|---|---|
| Arbitrary values | ✓ Yes | ✗ No | ✗ No |
| Custom step size | N/A | ✗ Fixed ±1 | ✓ via by |
| Exact output length | Manual (count args) | Implicit from range | ✓ via length.out |
| Named elements | ✓ Yes | ✗ No | ✗ No |
| Safe for 0-length | Returns NULL | ✗ 1:0 bug | ✓ seq_len(0) → integer(0) |
| Typical use case | Assembling data, merging vectors | Quick index ranges, loop counters | Evaluation grids, linspace-like sequences |
c() as manually placing books on a shelf in any order, the colon operator as telling someone to number shelves 1 through n, and seq() as a CNC machine that cuts shelves at precisely specified intervals. When precision and edge-case safety matter—as they do in research-quality code—seq() and its variants (seq_len, seq_along) are the safest choice.Connection to Advanced Data Structures
Vectors are not just a standalone concept—they are the atomic building blocks from which every complex data structure in R is assembled. Matrices and arrays are vectors with a dim attribute, data frames are lists of equal-length vectors, and even factors are integer vectors with a levels attribute. Mastering vector creation thus directly transfers to constructing and manipulating these higher-order structures.
| Concept from This Lesson | Advanced Extension | Example |
|---|---|---|
c() for flat vectors | list() for heterogeneous collections | list(name="Ada", age=36, scores=c(95,87)) |
seq() for 1D grids | expand.grid() for multi-dimensional grids | expand.grid(x=seq(0,1,0.1), y=seq(0,1,0.1)) |
Coercion in c() | Factor encoding via factor() | factor(c("low","med","high"), ordered=TRUE) |
Vectorized creation with rep() | Matrix construction via matrix() | matrix(1:12, nrow=3, ncol=4) |
As you progress to working with the tidyverse and data.table ecosystems, the fluency you develop now with atomic vector creation will pay dividends. Column operations in dplyr's mutate(), filtering with logical vectors, and constructing model matrices all rely on the same principles of type homogeneity, coercion, and sequence generation covered in this lesson.
Practice Problems
c(TRUE, 3L, 2.5, "hello") produces a character vector rather than a numeric or logical one. What is the coercion hierarchy that R follows, and why does it exist?seq() to generate the sequence 2, 4, 6, 8, 10. Then write an equivalent expression using the colon operator and vectorized arithmetic.x and applies a transformation to each element. Your loop currently uses for (i in 1:length(x)). A colleague passes x <- c() (an empty input). Describe the bug that occurs and rewrite the loop header to fix it.x <- seq(0, 1, by = 0.1); 0.3 %in% x. This returns TRUE on most systems. Now consider y <- seq(0, 1, by = 0.01); 0.29 %in% y. Under what circumstances might this return FALSE despite 0.29 appearing to be in the sequence? Propose a robust solution.Summary — Creating Vectors in R
Vectors are the fundamental atomic data structure in R, and three primary tools exist for creating them. The c() function concatenates arbitrary values into a single flat vector, applying implicit type coercion when mixed types are present (following the hierarchy logical → integer → double → character). The colon operator (:) provides a concise way to generate unit-step sequences, though its high precedence demands careful parenthesization in compound expressions. The seq() function generalizes sequence creation with by and length.out parameters for full control over step size and output length.
For defensive programming, prefer seq_len(n) over 1:n and seq_along(x) over 1:length(x) to avoid the classic 1:0 bug on zero-length inputs. These vector creation tools are the foundation upon which matrices, data frames, lists, and all higher-order R structures are built.