Short answer
The Field Calculator is QGIS's tool for transforming attribute values in bulk using the expression engine. For data cleanup it does four jobs: standardising text (case, whitespace, controlled vocabularies), fixing nulls and placeholders, recoding values, and recomputing geometry-derived fields like area and length. The single most important habit is to run it inside an edit session you can undo, on a copy of important data, and to update fields in place rather than spawning a dozen half-cleaned columns. Get the expression right against the live preview before you commit.
Where it lives and how it behaves
Open the attribute table (F6 or the table icon), then the Field Calculator (the abacus icon, or Ctrl+I). You get two modes:
- Update an existing field — overwrites values in a column you choose. This is what cleanup usually wants.
- Create a new field — adds a column of a chosen type and width.
A crucial detail: if a row selection is active, the calculator only updates selected features (the Only update selected features checkbox). That is a feature, not a bug — it lets you fix a subset — but it is a frequent source of "why did only half my rows change?". Clear the selection to process everything.
The calculator runs on the edit buffer. Changes are not written to disk until you click Save Edits, so Ctrl+Z reverts them while editing. For irreversible bulk operations on critical data, Save As a copy first.
String cleanup recipes
Inconsistent text is the most common mess in inherited GIS data. The string functions handle nearly all of it.
- Trim whitespace:
trim("unit")removes leading and trailing spaces. Stray trailing spaces are invisible but break joins and dissolves. - Standardise case:
upper("unit"),lower("email"), ortitle("formation")for proper-case names. - Fix known typos / recodes:
replace("unit", 'JR', 'JL'), or for several at once pass arrays:replace("unit", array('JR','KS2'), array('JL','Ks')). - Strip non-printing characters and collapse internal spaces:
regexp_replace(trim("name"), '\\s+', ' ')reduces double spaces to single. - Pad codes to fixed width:
lpad("id", 5, '0')turns42into00042for sortable IDs.
A typical one-shot standardisation of a unit code, updating the field in place:
trim(upper(replace("map_unit", array(' ','-'), array('',''))))
This removes spaces and hyphens, uppercases, and trims — turning j-l , JL, and Jl into a single canonical JL.
Handling nulls and placeholders
Empty values arrive as true NULL or as placeholder strings like '', 'N/A', '-9999'. Normalise them:
case
when "thick_m" in ('', 'N/A', '-9999') then NULL
when "thick_m" < 0 then NULL
else "thick_m"
end
To fill a fallback instead: coalesce("source", 'unknown'). Remember NULL propagates through arithmetic and ||, so a numeric field that mixes NULL and text placeholders will silently corrupt sums until you clean it.
Recomputing geometry fields
After digitising or reshaping polygons, stored area/length columns go stale. Recompute them — but mind the CRS, because $area and $length return values in the units of the layer CRS:
- In a projected CRS (EPSG:32633, EPSG:27700, a state plane zone)
$areais square metres or square feet — correct. - In a geographic CRS (EPSG:4326)
$areais square degrees — meaningless for size.
The safe expression uses the ellipsoidal area() function, which respects the project's configured ellipsoid:
round(area($geometry), 1)
Set the measurement ellipsoid under Project Properties > General (e.g. WGS 84) so ellipsoidal area is accurate regardless of the layer CRS. For length similarly use length($geometry). If you specifically want planar metric values, reproject the layer first with native:reprojectlayer to a suitable projected CRS, then use $area.
Worked example: cleaning an inherited shapefile
You receive outcrops.shp with UNIT (mixed case, trailing spaces, typos), AGE (some N/A), and a stale AREA_HA.
- Save As GeoPackage — escape shapefile's 10-character field names and 254-char text limits, and get a recoverable copy.
- Standardise UNIT in place:
trim(upper("UNIT")). Then Layer > Filter on"UNIT" NOT IN (list of valid codes)to surface remaining typos and fix them with targetedreplace(). - Null the placeholders: update
AGEwithcase when trim("AGE") in ('N/A','','-') then NULL else trim("AGE") end. - Recompute area: set the project ellipsoid, then update
AREA_HAwithround(area($geometry)/10000, 2)(square metres to hectares). - Verify, then Save Edits. Use the statistics panel to confirm the distinct UNIT count matches the legend and that area totals are plausible.
Common pitfalls and why they happen
- Only half the rows changed. Cause: an active selection plus Only update selected features. Fix: clear the selection.
- Area is tiny or huge. Cause:
$areain a geographic CRS, returning square degrees. Fix: usearea($geometry)with a set ellipsoid, or reproject first. - Field truncated. Cause: shapefile string fields default to 254 chars, integers to short widths. Fix: work in GeoPackage, or set adequate field width when creating.
- Changes lost after a crash. Cause: never clicked Save Edits — edits live only in the buffer. Fix: save periodically; conversely, do not save until verified.
- Typos persist after replace(). Cause: trailing whitespace means the value never matched the search string. Fix:
trim()beforereplace(). - Numeric column won't sum. Cause: stored as text with placeholders. Fix: clean placeholders to NULL, then change field type via Refactor Fields (
native:refactorfields).
Validation
Always preview against the first feature in the dialog before running. Afterwards, sort the cleaned column to spot outliers, run Statistics for min/max/distinct counts, and check the unique-values list (in the field properties) to confirm the controlled vocabulary collapsed to the expected set. Compare a known feature's recomputed area against a manual measurement. Only then Save Edits.
Bathyl perspective
Attribute cleanup is where a dataset earns the right to be analysed. We standardise keys before any join or dissolve, normalise nulls so aggregates are honest, and recompute geometry fields against a defined ellipsoid rather than trusting inherited numbers. Doing this in a recoverable edit session, on a copy, means the cleanup is auditable and never destroys the source.
Related reading
- QGIS Joins for Spatial Project Data
- QGIS Spatial Join Explained
- QGIS Dissolve for Geological Units
- GIS and spatial analysis