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.

A statistical scatter plot with code braces and a variable symbol for R programming.
Inside this lesson 10 sections 5 questions 8 figures

SECTION 1

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.

SECTION 2

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.

SECTION 3

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 Ways to Create Vectors in Rc() — Concatenatec(10, 25, 7, 42)1025742[1][2][3][4]Flattens nested calls:c(1, c(2,3)) → 1 2 3Type coercion:c(1, "a") → "1" "a"✓ Any values✓ Any order✓ Named elements: — Colon Operator3:734567[1][2][3][4][5]Descending also works:5:1 → 5 4 3 2 1Precedence warning:1:3+1 means 1:4, not 2:4✓ Step = ±1 only✓ Fast & concise✓ High precedenceseq() — Sequenceseq(0, 1, by=0.25)0.00.250.50.751.0[1][2][3][4][5]By length instead:seq(0, 1, length.out=5)Along another vector:seq_along(x) → 1 2 ... n✓ Custom step size✓ Exact output length✓ Most flexible
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.

SECTION 4

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→characterlogical → 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,…,bwherestep=sign(b−a)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⌋+1length.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.

SECTION 5

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.

Decision Tree: Choosing a Vector ConstructorNeed to create a vector?Are the values a regular sequence?NoYesc()arbitrary values / namesIs the step exactly ±1?NoYesa:bfast integer sequenceNeed exact # of elements?No (use by)Yesseq(from, to, by)custom step sizeseq(from, to, length.out)exact element countTip: In loops, prefer seq_len(n) over 1:n and seq_along(x) over 1:length(x) to handle n=0 safely.
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

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.

Common edge cases when constructing vectors in R

⚠

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.

SECTION 6

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. 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. 2

    Step 2 — Create an index vector with the colon operator

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

    Step 4 — Verify the constructed vectors

    We 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. 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)

SECTION 7

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.

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 NULL✗ 1:0 bug✓ seq_len(0) → integer(0)
Typical use caseAssembling data, merging vectorsQuick index ranges, loop countersEvaluation grids, linspace-like sequences

Feature comparison of R's three primary vector constructors

✦

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.

SECTION 8

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 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)

How vector concepts scale to advanced R data structures

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.

SECTION 9

Practice Problems

Question 1: CONCEPTUAL

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?
Show answer
R's atomic vectors enforce type homogeneity, so all elements must share a single type. The coercion hierarchy is logical → integer → double → character. Since "hello" is a character value—the most general type—all other elements are coerced to character: TRUE becomes "TRUE", 3L becomes "3", and 2.5 becomes "2.5". This hierarchy exists because every value can be represented as a string without loss of identity (though numeric semantics are lost), making character the "widest" type.

Question 2: BASIC 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.
Show answer
Using seq: seq(from = 2, to = 10, by = 2). Using the colon operator with vectorized multiplication: 2 * (1:5). Both produce the numeric vector [1] 2 4 6 8 10. The parentheses around 1:5 are technically unnecessary here (since : binds tighter than *), but they improve readability and guard against precedence surprises.

Question 3: INTERMEDIATE

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.
Show answer
The code is x <- seq(from = -pi, to = pi, length.out = 50). When length.out is specified, R computes by = (to − from) / (length.out − 1) = (π − (−π)) / 49 = 2π / 49 ≈ 0.1282. This formula ensures that both endpoints are included in the output. The resulting vector has exactly 50 elements uniformly partitioning the interval [−π, π].

Question 4: APPLIED

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.
Show answer
When x is empty, length(x) returns 0, so 1:length(x) evaluates to 1:0, which is the descending sequence c(1, 0). The loop body executes twice, attempting to access x[1] and x[0], both of which return unexpected results (NA or a zero-length vector). The fix: for (i in seq_along(x)). When x has length 0, seq_along(x) returns integer(0) and the loop body never executes.

Question 5: CRITICAL 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.
Show answer
IEEE 754 floating-point arithmetic cannot represent 0.01 exactly in binary, so cumulative additions in seq() may produce values like 0.29000000000000004 instead of exactly 0.29. The %in% operator uses exact equality (==), which fails for values that differ by even a single ULP (unit in the last place). A robust solution is to use any(abs(y - 0.29) < .Machine$double.eps^0.5), which checks for near-equality within machine tolerance. Alternatively, when exact membership testing is critical, construct the sequence using integer arithmetic and scale: y <- (0:100) / 100.

SUMMARY

Summary — Creating Vectors in R

Summary

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()