ggplot2 coord_cartesian() in R: Zoom Without Dropping Data
ggplot2 coord_cartesian() in R zooms a plot to a specific x or y window without filtering the underlying data, so smoothers, regression lines, and statistical layers keep using every row. It is the safe alternative to xlim() and scale_x_continuous(limits = ...), both of which silently drop points outside the window before stats run.
coord_cartesian(xlim = c(0, 50)) # zoom the x axis coord_cartesian(ylim = c(10, 30)) # zoom the y axis coord_cartesian(xlim = c(0, 50), ylim = c(10, 30)) # zoom both coord_cartesian(expand = FALSE) # remove default padding coord_cartesian(clip = "off") # let labels spill outside coord_cartesian(default = TRUE) # use as a base coord ggplot(df, aes(x, y)) + geom_point() + coord_cartesian(xlim = c(0, 10)) ggplot(df, aes(x, y)) + geom_smooth() + coord_cartesian(ylim = c(0, 5))
Need explanation? Read on for examples and pitfalls.
What coord_cartesian() does in one sentence
coord_cartesian() is a coordinate system that crops the plot panel to a window without removing any data from the layers. Limits are applied at the rendering stage, after every stat has run, so smoothers, density curves, and regression lines see the full dataset and the visible part of the curve stays correct. By contrast, scale_x_continuous(limits = c(a, b)) and the shortcut xlim(a, b) both drop rows outside the window before the stat sees them, which silently changes the result.
Scale limits act before stats; coord limits act after.
Syntax
coord_cartesian() takes four arguments and zero are required.
xlimandylim: length-2 numeric (or date/time) vectors. NULL means use the data range.expand: set toFALSEfor bar charts or area charts that should start exactly at zero.default: lets a wrapper function fall back to cartesian if the caller has not requested another coord.clip: switch to"off"when geom_text() labels or arrows need to render beyond the panel border.
coord_*() call already runs through cartesian coordinates. You only call coord_cartesian() explicitly when you need to zoom, change expansion, or alter clipping; you do not need it to draw a normal plot.Five common patterns
Pattern 1: zoom into a busy scatter plot without dropping points. The classic use case. A scatter has a long tail or a sparse edge; you want to inspect the dense region but keep the smoother fitted on all data so the trend you read off the visible curve still reflects the full sample.
Pattern 2: same plot, wrong way (drops data before the smoother). Passing limits to the scale removes rows below 2 and above 4, so the regression line is computed on a different sample. The console warning about removed rows is your hint that ggplot2 silently filtered the data.
Pattern 3: bar chart with zero-anchored y axis. Bar charts should start at zero. Combine coord_cartesian(ylim = ...) with expand = FALSE to clip the upper end without floating the baseline.
Pattern 4: keep a smoother continuous across a panel zoom. With coord_cartesian(), the loess curve in the visible window is exactly the curve fitted to the full dataset, restricted to that window. This matters most for loess and gam because both methods borrow strength from neighboring observations.
Pattern 5: clip = "off" lets annotations escape the panel. Use this when geom_text() labels would otherwise be cut off at the panel edge.
coord_cartesian() vs scale_*_continuous(limits) vs xlim()
Three routes to "smaller plot," but only one preserves your data.
| Feature | coord_cartesian() | scale_x_continuous(limits) | xlim(a, b) |
|---|---|---|---|
| Drops data outside the window | no | yes | yes |
| Stats see the full dataset | yes | no | no |
| Issues "Removed N rows" warning | no | yes | yes |
| Can transform the scale (log, sqrt) | no | yes | no |
| One-line shortcut | medium | verbose | short |
| Correct for zoom intent | yes | rarely | rarely |
Common pitfalls
Pitfall 1: passing limits to xlim() and being surprised by the regression. A line that looks fine over the full data shifts when you call xlim() to focus on a region. The shift is not a rendering quirk; it is a refit on the subsetted data. Switch to coord_cartesian() and the slope returns to the original value.
Pitfall 2: coord_cartesian() ignored because another coord was set. A plot can have only one coord. If you have already called coord_flip() or coord_polar(), adding coord_cartesian() overwrites the earlier call. Combine zoom with flipping by passing xlim and ylim to coord_flip() itself; those arguments behave the same way.
Pitfall 3: expanding when you wanted exact limits. coord_cartesian(ylim = c(0, 100)) still adds a 5 percent buffer on each side. For a bar chart that needs to sit on the x axis, you must also pass expand = FALSE, otherwise the baseline floats slightly below zero.
Pitfall 4: zooming a plot that uses a log or sqrt scale. coord_cartesian() works on the transformed scale, not the original units. After scale_y_log10(), the limits c(10, 100) passed to coord_cartesian() are read in log space. Compute the log values yourself: coord_cartesian(ylim = log10(c(10, 100))) when you want data-space numbers.
Try it yourself
Try it: Build a scatter plot of mpg vs hp from the mtcars dataset with a linear smoother. Zoom the x axis to 100 to 200 hp without changing the slope of the smoother. Save the plot to ex_zoom.
Click to reveal solution
Explanation: Because coord_cartesian() runs after the smoother is fitted, the line is identical to the unzoomed plot, just clipped to the visible window. Using xlim(100, 200) instead would refit the line on the subset of cars in that window and report a different slope.
Related ggplot2 functions
coord_flip(): rotates the plot 90 degrees; also accepts xlim and ylim.coord_polar(): bends an axis into a circle for pies and rose plots.coord_fixed(): enforces a fixed aspect ratio, useful for maps and equal scaling.scale_x_continuous(): transforms or filters the x axis depending on whether you pass limits.xlim()andylim(): shortcut wrappers around the continuous scales; both filter data.
FAQ
What is the difference between coord_cartesian and xlim in ggplot2?
coord_cartesian(xlim = c(a, b)) zooms the panel to the window after all stats and geoms have computed; every row of data still participates in fitting smoothers and computing densities. xlim(a, b) is a shortcut for scale_x_continuous(limits = c(a, b)), which sets the limits on the scale and drops any row outside the window before the stat runs. The shortcut also prints a "Removed N rows" warning. Use coord_cartesian() when you want to zoom; use xlim() when you want to filter.
Does coord_cartesian() work with geom_smooth and statistical layers?
Yes, and it is the recommended way to zoom anything that uses a stat. Because the coord applies its limits after the stat has computed, geom_smooth(), geom_density(), stat_summary(), and stat_quantile() all see the full data. Passing limits to scale_x_continuous() instead would change the smoother fit and the density estimate because the stat would only see the rows inside the window.
Why does my bar chart float above zero after coord_cartesian(ylim = c(0, 100))?
The default expand = TRUE adds a 5 percent buffer on each side of the window, which lifts the y axis baseline slightly below zero for the gridlines but visually leaves a gap between the bars and the axis. Pass expand = FALSE to anchor the bars exactly at zero, or use scale_y_continuous(expand = expansion(mult = c(0, 0.05))) for asymmetric padding that keeps zero pinned.
Can I combine coord_cartesian with coord_flip?
No. A ggplot2 plot can have only one coord, so the second call overwrites the first. If you need a flipped plot zoomed to a window, pass xlim and ylim to coord_flip() directly: coord_flip(xlim = c(0, 50)). The arguments behave the same as in coord_cartesian() because coord_flip() inherits from the cartesian coord.
External reference: ggplot2 coord_cartesian() documentation.