R PROGRAMMING • DATA STRUCTURES IN R

Creating Vectors — Create vectors with c() and sequences with : and seq()

Master the foundational data structure of R by constructing vectors through concatenation, colon notation, and the versatile seq() function.

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.

1976
S Language at Bell Labs
John Chambers and colleagues at Bell Labs created the S language, introducing the idea that statistical data should be stored in vector and matrix objects with built-in vectorized operations—a radical departure from Fortran-style loops.
1988
S Version 3 and c()
S3 formalized the c() function for concatenating values into vectors, establishing the syntax that R would later adopt almost verbatim.
1993
R is Born
Ross Ihaka and Robert Gentleman at the University of Auckland began developing R as a free implementation of the S language, preserving vector-first semantics and adding the colon operator for integer sequences.
2000
R 1.0.0 Release
The stable release of R 1.0.0 solidified 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.

1

Atomic Homogeneity

An atomic vector in R holds elements of exactly one type: logical, integer, double, character, or complex. Mixing types triggers implicit coercion following the hierarchy logical → integer → double → character.
2

Scalars Are Length-1 Vectors

R has no true scalar type. Writing x <- 5 creates a numeric vector of length one. This uniformity means every vector creation tool works consistently regardless of the number of elements.
3

c() Concatenates Recursively

The 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.
4

The Colon Operator Generates Integers

The expression 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.
5

seq() Provides Full Control

The 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.
KEY TAKEAWAY
Think of a vector like a single row of mailboxes in an apartment building. Each mailbox (element) must be the same size and shape (same type), they are numbered sequentially starting at 1, and 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.

Three columns compare 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.

TYPE COERCION HIERARCHY
logical → integer → double → complex → character
Each arrow represents an implicit widening conversion. 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.

COLON OPERATOR SEMANTICS
a:b = {a, a ± 1, a ± 2, …, b} where step = sign(b − a)
When 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).

seq() RELATIONSHIP
length.out = ⌊(to − from) / by⌋ + 1
When 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.

A decision tree guiding the choice among 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

Common edge cases when constructing vectors in R
ExpressionResultExplanation
c()NULLNo arguments produces NULL, not an empty vector. Use integer(0) for an empty typed vector.
1:01 0The 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.9The endpoint 1.0 is excluded because 0.9 + 0.3 = 1.2 > 1.0. Use length.out if both endpoints must appear.
⚠️ Defensive Programming Tip
In for-loops, always use 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.

Building Vectors for a Simulation
1
Step 1 — Create the sample-size vector with c()We manually specify five sample sizes that the simulation will iterate over. Since these are arbitrary, non-sequential values, c() is the appropriate tool.
sample_sizes <- c(50, 100, 500, 1000, 5000)
2
Step 2 — Create an index vector with the colon operatorFor iterating over 10 replications, we need the integers 1 through 10. The colon operator is ideal for this unit-step integer sequence.
reps <- 1:10[1] 1 2 3 4 5 6 7 8 9 10
3
Step 3 — Create the evaluation grid with seq()We need exactly 100 points spanning the interval [0, 2π]. Since 2π/99 is an irrational step size, we specify the desired number of elements rather than a step. This guarantees that both 0 and 2π appear in the output.
theta <- seq(from = 0, to = 2 * pi, length.out = 100)
4
Step 4 — Verify the constructed vectorsWe use 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.1904
5
Step 5 — Combine results using c()After the simulation, suppose we want to concatenate partial result vectors from two runs. Since c() 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 comparison of R's three primary vector constructors
Featurec(): (colon)seq()
Arbitrary values✓ Yes✗ No✗ No
Custom step sizeN/A✗ Fixed ±1✓ via by
Exact output lengthManual (count args)Implicit from range✓ via length.out
Named elements✓ Yes✗ No✗ No
Safe for 0-lengthReturns NULL1:0 bugseq_len(0)integer(0)
Typical use caseAssembling data, merging vectorsQuick index ranges, loop countersEvaluation grids, linspace-like sequences
KEY TAKEAWAY
Think of 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.

How vector concepts scale to advanced R data structures
Concept from This LessonAdvanced ExtensionExample
c() for flat vectorslist() for heterogeneous collectionslist(name="Ada", age=36, scores=c(95,87))
seq() for 1D gridsexpand.grid() for multi-dimensional gridsexpand.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

PROBLEM 1CONCEPTUAL
Explain why 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?
PROBLEM 2BASIC CALCULATION
Write an R expression using seq() to generate the sequence 2, 4, 6, 8, 10. Then write an equivalent expression using the colon operator and vectorized arithmetic.
PROBLEM 3INTERMEDIATE
You need a vector of 50 evenly spaced values from −π to π, inclusive of both endpoints. Write the R code. Then determine, without running the code, what the step size between consecutive elements will be.
PROBLEM 4APPLIED
You are writing a function that accepts a vector 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.
PROBLEM 5CRITICAL THINKING
Consider the expression 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.

Varsity Tutors • R Programming • Creating Vectors — Create vectors with c() and sequences with : and seq()