QGIS: maps for development data
Turn a district table into a map you can put in a report: load openly licensed boundaries, join your survey figures to them by district name, choose how to group the values into colours, write a legend that does not mislead, and export a print layout. QGIS is free. Python cells on this page prepare the table and show what the join and the classification choices do to the same data.
Install QGIS: long-term release or latest
QGIS is a free and open source geographic information system. Its home page says it is "licensed under GNU GPLv2+", and the download page says the software "is, and always will be, available free of charge if downloaded from QGIS.org". The download page asks for an optional donation; you can skip it.
Which one to choose
The download page says LTR builds "are intended for those who value stability over having the latest features", and "if you are unsure which version is best for you, download the LTR". For a programme team that shares project files, choose the LTR and have everyone use the same one. The menu paths on this page are from the QGIS 3.44 user manual; the 4.x releases may place some items differently.
- Windows: the download page offers the OSGeo4W network installer (it describes this as the best way to keep QGIS up to date and run several versions) and standalone installers for the LTR (3.44) and the latest (4.2). Since QGIS 3.20 only 64-bit Windows executables are shipped. On a slow connection, the standalone installer is one file you can copy to a USB stick for colleagues.
- macOS: signed installers for the LTR (3.44) and the latest (4.2).
- Linux: packages for each distribution, chosen on the same page.
Layers, the map canvas and coordinate systems
A QGIS project (a .qgz file) holds a list of layers and how each is drawn. The project file stores the path to your data, so keep the project and its data in one folder and move them together.
- The Layers panel lists the layers. A layer higher in the list is drawn on top. Tick a layer to show or hide it.
- The Browser panel shows folders and data sources; you can drag a file from it onto the map.
- The map canvas draws the visible layers.
- A vector layer holds features (points, lines or polygons) with an attribute table: one row per feature. District boundaries are polygons, one row per district.
Coordinate reference systems
Every layer has a coordinate reference system (CRS) that says how its numbers map to places on the earth. DataMeet's boundaries are in WGS84, EPSG:4326 (latitude and longitude in degrees), as their project page states. QGIS shows the project's CRS at the bottom right of the window. For a district choropleth of India, WGS84 is fine to start with; if you measure areas or distances, reproject to a projected CRS first, because degrees are not a unit of length.
Opening the attribute table
Select a layer in the Layers panel and choose Layer ► Open Attribute Table, or right-click it and choose Open Attribute Table, or press F6. Do this every time you load or join something: the table is where mistakes show first.
Load district boundaries
Where to get boundaries, and their licences
- DataMeet, Community Created Maps of India (projects.datameet.org/maps). The district boundaries page says the dataset is "shared under Creative Commons Attribution 2.5 India license" (CC BY 2.5 IN), and gives an attribution line to use. The repository README says that, unless stated otherwise, its datasets are under CC BY 4.0, so read the licence stated for the folder you download: the files differ. The district file sits at
Districts/Census_2011/in the repository, and its README says the names and extents come from the Census of India 2011 Administrative Atlas. The project page warns that the data "is not perfect". - GADM (gadm.org, version 4.1 on the download page). Its licence page says the data "are freely available for academic use and other non-commercial use. Redistribution or commercial use is not allowed without prior permission." It allows maps made with GADM data in published academic articles. For an NGO report or a consultancy deliverable, check with GADM first.
Load the layer
- Download the district shapefile from DataMeet. A shapefile is several files with the same name and different extensions (
.shp,.shx,.dbf,.prj, sometimes more). Keep them together in one folder; the.shpalone is useless. - In QGIS choose Layer ► Add Layer ► Add Vector Layer… (Ctrl + Shift + V), or open the Data Source Manager with Ctrl + L.
- Select the
.shpfile (or a.geojsonfile, which is a single file) and click Add. - Open the attribute table (F6). In the DataMeet 2011 district file the fields are
DISTRICT,ST_NM(state name),ST_CEN_CD,DT_CEN_CDandcensuscode. When we read the file on 6 October 2026 it held 641 districts.
Join a CSV to the boundaries
A choropleth needs one value per district in the boundary layer's attribute table. Your survey figures are in a separate table, so you join them, matching rows by a field the two tables share.
Make the district table
The course files are households.csv (240 households) and districts.csv (10 districts). Illustrative data, invented for teaching The district names are real places; every number is made up, so a map of them says nothing about the real districts. The cell summarises the households to one row per district and prints it as CSV for you to save.
Each district has 24 households. Mean monthly per capita expenditure runs from Rs 2,395.0 in Purnia to Rs 6,757.5 in Kozhikode, and the share of households with a toilet from 50.0% in Rewa to 83.3% in Indore, Kozhikode and Patna. Copy the CSV lines into a text file called district_summary.csv.
Load the CSV and join it
- Open the Data Source Manager (Ctrl + L) and choose the Delimited Text tab. Browse to
district_summary.csv, choose CSV as the format, and under geometry choose No geometry (attribute only table). Add it. It appears in the Layers panel as a table. - Open the boundary layer's properties: double-click the layer, or right-click it and choose Properties…. Go to the Joins tab and click the Add new join button.
- Set Join layer to
district_summary, Join field todistrictand Target field toDISTRICT. - Under Joined fields, tick only the columns you need, and set a short Custom field name prefix such as
s_so the new columns are easy to find. Click OK, then OK again. - Open the attribute table. The ten districts now carry values; the other 631 rows hold NULL in the joined columns. That is correct here, because the survey covers ten districts.
The QGIS manual describes this join as one-to-one, based on an attribute that both layers share, so the values must match exactly, letter for letter.
The name-mismatch problem
District names in survey and MIS files rarely match a boundary file exactly. The cell below joins a hand-typed MIS table to the ten names as they appear in the DataMeet file.
Only 5 of 10 districts match. Betul, Gaya, Kozhikode, Patna and Purnia get no value, because the MIS typed Baitul, Gaya with a trailing space, Calicut, PATNA and Purnea. QGIS would give no warning: those districts would simply be blank on the map, which reads as "no data" or, with some colour ramps, as "lowest".
After trimming spaces, fixing case and a three-line lookup, all 10 match. The second part shows the other trap: Aurangabad is a district in both Bihar and Maharashtra (the DataMeet 2011 file has both, along with other repeated names such as Bilaspur, Hamirpur and Pratapgarh), so joining on the name alone turns one row into two. Join on district and state, or better, on a code.
censuscode and the spelling problem disappears. If it carries only names, clean them first (see the OpenRefine course) and keep the lookup table with your project, so the next round's join uses the same fixes."Calicut": "Kozhikode" from the lookup and run. The count drops to 9 of 10, and the printed table shows which district lost its value.Choropleth styling and classification
- Open the boundary layer's properties and go to Symbology.
- Change the renderer at the top from Single Symbol to Graduated.
- Set Value to the joined field, for example
s_mean_pc_exp. - Pick a Color ramp: one hue from light to dark for a quantity where more is more.
- On the Classes tab, set the number of classes and the Mode, then click Classify. The Histogram tab shows where the class breaks fall on the distribution of values.
The classification modes
The QGIS manual lists these modes for a graduated renderer:
- Equal Interval: every class covers the same range of values.
- Equal Count (Quantile): every class holds the same number of features.
- Natural Breaks (Jenks): "the variance within each class is minimized while the variance between classes is maximized".
- Also: Fixed Interval, Logarithmic scale, Pretty Breaks (round numbers) and Standard Deviation.
Same data, different modes, different maps. The cell below puts the ten district means into four classes three ways. The breaks come from code written for this course (numpy quantiles, and an exact search for natural breaks); QGIS computes its own, which may differ slightly, so compare them with the Histogram tab.
The three methods disagree. Equal interval (breaks at about Rs 3,486, 4,576 and 5,667) puts six districts in the lowest class, three in the second, none in the third and only Kozhikode in the top, because Kozhikode's Rs 6,757.5 stretches the range. Quantile puts Indore, Wayanad and Kozhikode together in the top class, although Kozhikode's mean is Rs 2,800 above Wayanad's, and it separates Udaipur (class 3) from Indore (class 4), which are Rs 147 apart. Natural breaks (about Rs 2,569, 2,962 and 3,957) keeps Udaipur, Indore and Wayanad together and leaves Kozhikode alone at the top. Three honest-looking maps of the same ten numbers would tell three different stories about Indore.
The chart draws each district as a dot on one line and the class breaks as dashed lines. In the equal-interval panel two of the three breaks fall in the empty stretch between Wayanad and Kozhikode; in the quantile panel the breaks crowd into the cluster of low values; the natural-breaks panel ends each class at the last district before a wide gap, so its lines sit on Patna, Betul and Wayanad.
Why the choice matters
- Equal interval is easy to read ("Rs 500 bands"), and honest about distances between values, but one outlier leaves most districts in one colour.
- Quantile uses every colour equally, which makes any map look varied. Two districts Rs 30 apart can land in different colours, and a reader will assume they differ.
- Natural breaks fits this particular dataset, so the classes change when the data change: two survey rounds classed this way cannot be compared colour for colour.
- For a series of maps (two rounds, or the same indicator across states), fix the breaks by hand with round numbers, and use the same breaks on every map.
k = 4 to k = 3 and run. Does any method move Indore out of a class of its own?Honest legends
A legend is part of the claim the map makes. In the Classes list of the Graduated renderer, double-click a label to edit it; the QGIS manual notes you can also set the legend format and its precision. An empty class, like the third equal-interval class in the last module, still shows in the legend: delete it or change the breaks.
- State the unit and the measure: "Mean monthly per capita expenditure (Rs), households surveyed". A bare "Expenditure" leaves the reader guessing.
- Round the class labels to what the data can support. "Rs 2,395.0 to 2,569.2" claims a precision that 24 households per district cannot give; "Rs 2,400 to 2,600" does not.
- Show no-data districts as no data, in grey, with their own legend entry. A blank district can read as zero. One way: put a second copy of the boundary layer underneath, styled with a single grey fill, and name it "No survey data".
- Use one hue, light to dark, for a quantity. Rainbow ramps have no natural order, and red-green ramps fail for many colour-blind readers.
- Say how many observations stand behind each value, in a note or a label. A district mean from 24 households is a rough estimate; map the sample size too if it varies.
- For survey estimates, use the weights. NFHS household weights are
hv005 / 1,000,000and women's weightsv005 / 1,000,000; compute the weighted district value before you join it, in your statistics software. QGIS styles the numbers it is given and applies no survey weights. - Write the source and the licence on the map: the survey, its year, and the boundary file's attribution line.
Publishing a map of India
Maps of India that show the national or state boundaries carry an extra requirement. The Department of Science and Technology's Guidelines for acquiring and producing Geospatial Data and Geospatial Data Services including Maps (F.No.SM/25/02/2020 (Part-I), dated 15 February 2021) say in paragraph xiii:
SoI is the Survey of India. Its website lists a Political Map of India, an Outline Map of India and state maps among its products. The same guidelines' paragraph xv says violations "will be dealt with under the applicable laws".
What that means for your map
- If your map shows India's outline or state boundaries, the outline must match the Survey of India's, including the boundaries in Jammu and Kashmir and Ladakh and in Arunachal Pradesh. Compare your boundary file with the Survey of India's political map before you publish.
- Global boundary sets do not necessarily follow it. GADM, Natural Earth and many web basemaps draw India's borders differently in places, so do not assume a downloaded file complies.
- DataMeet's repository has a
Countryfolder whose README describes an India outline "in accordance with the Official boundary of India as per the Survey of India" (india-composite.geojson, licensed CC0) and a fileindia-soi.geojson, which it describes as "dissolved shapefiles from the official Indian shapefile data available" (licence given as CC-by-sa 2.5 / ODbL). Check either against the Survey of India map yourself. - A map of a few districts with no national or state outline still benefits from a state boundary for context; take that boundary from a source that follows the official one.
Export a print layout
A print layout is a page on which you arrange the map, legend, scale bar, title and notes, and from which you export a PDF or an image. The steps follow the QGIS 3.44 manual's print layout overview and output pages.
- Style the map in the main window first. Then choose Project ► New Print Layout… and give the layout a name.
- Click Add map on the toolbar and drag a rectangle on the page. The current map view is drawn inside it.
- Click Add legend and drag a rectangle. In the legend's item properties, remove layers that should not appear (the CSV table, for instance) and rename entries.
- Click Add scale bar and click on the page. Add a north arrow from the same toolbar if your audience expects one.
- Add labels for the title, the source line (survey, year, sample size) and the boundary attribution, for example DataMeet's suggested line with its licence.
- Export with Layout ► Export as PDF… or Layout ► Export as Image…. The manual notes that a PDF export puts every page of the layout into one file, and that image export writes one file per page.
- Save the project (Ctrl + S): the layout is saved inside it.
Where next
OpenRefine: cleaning messy survey and MIS data
Clean district and village names so the join matches.
Open Data Editor
Check the district table before you share it.
pandas for development data
Build weighted district summaries in code.
Data Visualization 101
Choosing charts and colours that tell the truth.
Survey Design 101
Sampling frames, districts and what an estimate can support.