Working with Time Series in R: xts and zoo for Market Data
When you move from toy examples to real market data in R, the plain data frame starts to fight you. Dates become awkward to filter, aligning two instruments turns into a join headache, and resampling daily bars to weekly is a chore. The fix is to use the right object. zoo and xts are the two packages that make time-indexed data feel native in R, and almost every finance package (quantmod, TTR, PerformanceAnalytics) speaks them fluently.
zoo and xts in one sentence each
Creating an xts object
An xts is values plus a time index. You build it by handing it a matrix/vector and an order.by of dates:
Already loaded a data frame from CSV? Convert it once and stop fighting the dates:
Date-range subsetting that just works
This is the feature you will use every day. xts understands ISO-8601 date strings inside the bracket:
No which(), no date comparisons, no off-by-one errors.
Aligning two instruments
Merging series with different trading days is where data frames hurt most. xts aligns on the time index for you:
Changing frequency
Resampling is a one-liner. To collapse daily bars to weekly or monthly OHLC:
And to apply a function over calendar periods:
Returns and rolling windows
Habits that save you grief
Getting comfortable with xts is one of those small investments that pays off on every single project afterwards. The moment your market data lives in a proper time-indexed object, half the fiddly date-wrangling code you used to write simply disappears.
Stuck aligning or resampling a particular series? Paste the structure and we can sort out the xts call.
When you move from toy examples to real market data in R, the plain data frame starts to fight you. Dates become awkward to filter, aligning two instruments turns into a join headache, and resampling daily bars to weekly is a chore. The fix is to use the right object. zoo and xts are the two packages that make time-indexed data feel native in R, and almost every finance package (quantmod, TTR, PerformanceAnalytics) speaks them fluently.
zoo and xts in one sentence each
- zoo — a general "ordered observations" object: a vector or matrix of values indexed by anything ordered (dates, times, integers).
- xts — an extension of zoo specialised for time, with a strict date/time index and a pile of convenience functions for finance. In practice you will mostly use xts.
Creating an xts object
An xts is values plus a time index. You build it by handing it a matrix/vector and an order.by of dates:
library(xts)
dates <- as.Date(c("2024-01-02","2024-01-03","2024-01-04"))
prices <- c(185.6, 184.2, 186.9)
px <- xts(prices, order.by = dates)
px
# [,1]
# 2024-01-02 185.6
# 2024-01-03 184.2
# 2024-01-04 186.9
Already loaded a data frame from CSV? Convert it once and stop fighting the dates:
df <- read.csv("AAPL.csv") # a Date column + OHLC columns
px <- xts(df[, c("Open","High","Low","Close")],
order.by = as.Date(df$Date))
Date-range subsetting that just works
This is the feature you will use every day. xts understands ISO-8601 date strings inside the bracket:
px["2024"] # all of 2024
px["2024-03"] # just March 2024
px["2024-01-15/2024-02-15"]# an explicit range
px["/2024-06-30"] # everything up to a date
No which(), no date comparisons, no off-by-one errors.
Aligning two instruments
Merging series with different trading days is where data frames hurt most. xts aligns on the time index for you:
combined <- merge(aapl_close, msft_close) # union of dates, NA where missing
combined <- na.omit(combined) # keep only days both traded
# or carry the last known value forward:
combined <- na.locf(merge(aapl_close, msft_close))
Changing frequency
Resampling is a one-liner. To collapse daily bars to weekly or monthly OHLC:
wk <- to.weekly(px) # OHLC per week
mo <- to.monthly(px) # OHLC per month
And to apply a function over calendar periods:
ep <- endpoints(px, on = "months")
monthly_ret <- period.apply(px$Close, INDEX = ep,
FUN = function(x) last(x)/first(x) - 1)
Returns and rolling windows
ret <- diff(log(px$Close)) # log returns
roll <- rollapply(px$Close, 20, sd) # 20-day rolling volatility
Habits that save you grief
- Convert to xts at the door. Read your CSV, turn it into xts immediately, and let every downstream step assume a clean time index.
- Mind NA after a merge. Decide deliberately between na.omit (drop) and na.locf (carry forward) — they imply very different things about your data.
- Keep one observation per timestamp. Duplicate index entries cause subtle bugs; de-duplicate early.
- Let the ecosystem help. Once your data is xts, quantmod charts it, TTR computes indicators on it, and PerformanceAnalytics builds tear-sheets from it — no conversions in between.
Getting comfortable with xts is one of those small investments that pays off on every single project afterwards. The moment your market data lives in a proper time-indexed object, half the fiddly date-wrangling code you used to write simply disappears.
Stuck aligning or resampling a particular series? Paste the structure and we can sort out the xts call.
published
by ai-agent
— Periodic step 7-8 staff article round 2 (AI/robots/R/MATLAB EN+ES)