Analysis of Household Surveys / University of Ghana

Install on your own computer


How to put Python and the survey library on your own laptop, assuming no prior Python. This is what you need in order to keep using the workshop materials once the shared server is gone.

Read this first

The workshop ran on a shared server, which closes at the end of September 2026. The six sessions are ordinary Jupyter notebooks and run anywhere the software below is installed. If you get stuck, tell us how far you got — open an issue at ligon/hhsurveys or ligon/LSMS_Library, or write. A failure report costs you nothing and is genuinely useful.

Do this at home, on good wifi

The download is roughly 1.3 GB, and the survey data adds a few hundred megabytes more. Please do not start it in the classroom or on mobile data — twenty laptops downloading at once will simply stall, and you will lose the session. Set aside an evening somewhere with a good connection.

Choose your computer

Every instruction below changes to match this choice.

The one-step install

This script does everything on the rest of this page: finds or installs Miniforge, creates the surveys environment, installs the library, asks for your World Bank key and stores it in the right place, then reads real survey data to prove it worked. It is safe to run more than once — anything already done is skipped — and it does not touch your shell startup files.

Open Terminal and paste these two lines:

curl -fsSL https://hhsurveys-workshop.ligonresearch.org/install_surveys.sh -o install_surveys.sh
bash install_surveys.sh

Open PowerShell from the Start menu and paste these three lines:

cd $HOME\Downloads
Invoke-WebRequest https://hhsurveys-workshop.ligonresearch.org/install_surveys.ps1 -OutFile install_surveys.ps1
powershell -ExecutionPolicy Bypass -File .\install_surveys.ps1

Windows blocks downloaded scripts by default, which is what -ExecutionPolicy Bypass is for. It applies to that one run and changes nothing on your machine.

Read it first if you like — it is plain text and commented throughout: install_surveys.sh · install_surveys.ps1.

If the script fails, the steps below still work

The manual route is the same thing done by hand, and it is what to fall back on — particularly on Windows, where far fewer people have run this. Either way, please tell us where it stopped: open an issue.

Or, the four steps by hand

  1. Install Miniforge

    Miniforge gives you Python and a way to install scientific packages. If you already have Anaconda or Miniconda, you can skip this step and use that instead.

    Download and run the installer, accepting every default:

    Miniforge3-Windows-x86_64.exe

    When it finishes you will have a new program in the Start menu called Miniforge Prompt. Every command on this page gets typed there — not in Command Prompt or PowerShell.

    Open Terminal (press Cmd + Space, type terminal, press Return), then paste these three lines one at a time:

    curl -L -O "https://github.com/conda-forge/miniforge/releases/latest/download/Miniforge3-MacOSX-$(uname -m).sh"
    bash Miniforge3-MacOSX-$(uname -m).sh -b
    ~/miniforge3/bin/conda init zsh

    Then close Terminal and open it again. You should see (base) at the start of the line.

    Open a terminal and paste these three lines one at a time:

    curl -L -O "https://github.com/conda-forge/miniforge/releases/latest/download/Miniforge3-Linux-$(uname -m).sh"
    bash Miniforge3-Linux-$(uname -m).sh -b
    ~/miniforge3/bin/conda init bash

    Then close the terminal and open it again. You should see (base) at the start of the line.

  2. Create the workshop environment

    This creates a separate workspace called surveys, so nothing here can disturb other software on your computer. Open the Miniforge Prompt from the Start menu and paste these two lines, one at a time:

    conda create -y -n surveys python=3.12 git
    conda activate surveys

    The first line downloads a few hundred megabytes and takes a while. When the second finishes, the line should begin with (surveys).

    This creates a separate workspace called surveys, so nothing here can disturb other software on your computer. Paste these two lines, one at a time:

    conda create -y -n surveys python=3.12 git
    conda activate surveys

    The first line downloads a few hundred megabytes and takes a while. When the second finishes, the line should begin with (surveys).

  3. Install the survey library

    With (surveys) showing at the start of your line, paste:

    pip install LSMS_Library datamat jupyterlab

    This is the big one — about 1.1 GB and roughly 180 packages. Expect anywhere from a few minutes to the better part of an hour, depending on your connection. It is finished when you get your prompt back and see no red ERROR text.

  4. Check that it worked

    Start Jupyter by pasting:

    jupyter lab

    A page opens in your browser. Click Notebook → Python 3, paste the five lines below into the box, and press Shift + Return.

    import lsms_library as ll
    
    ghana = ll.Country('GhanaLSS')
    print(ghana.waves)
    
    roster = ghana.household_roster()
    print(roster.shape)

    The first run downloads real survey data — about 34 MB. On a fast connection that takes a minute or two; on a slow one it can take considerably longer. Later runs take a couple of seconds, because the data is then stored on your machine.

    If it does not finish

    The years printing tells you only that the library loaded. What matters is what the cell does next, and there are two quite different possibilities:

    • The cell is still busy. Jupyter shows [*] beside it while it works. It is downloading the survey data — leave it running, even for ten minutes or more. This happens only on the first run.
    • You get a red Traceback. Read its last line, which names the real problem:
      • NoCredentialsError: Unable to locate credentials — no World Bank API key. See below; it is a five-minute fix, and free.
      • Bad git executable — git is missing; the surveys environment is probably not active.
      • Anything else — run the fuller check at the bottom of this page and send the two REPORT lines.

    On the workshop server these lines returned almost instantly, because the data was already staged there. On your own machine the first run downloads it, so give it time.

    What success looks like

    ['1987-88', '1988-89', '1991-92', '1998-99', '2005-06', '2012-13', '2016-17']
    (246594, 8)

    That is the Ghana Living Standards Survey: seven rounds spanning thirty years, and 246,594 people in the household rosters. If you see those two lines, you are done — please reply and say so.

    Harmless messages you can ignore

    Lines mentioning DVC unavailable, Falling back to manual aggregation, Issue with categorical mapping, or matplotlib building the font cache are all normal. They are not errors. Only the red Traceback blocks matter.

If something fails

These are the three failures we expect. In every case, the answer is to fix it if it is quick, and otherwise to stop and tell us — you will not be behind.

Python is too old

ERROR: Could not find a version that satisfies the requirement LSMS_Library

The library needs Python 3.11 or newer. This usually means step 2 was skipped, or an older Anaconda is in charge. Check with python --version; if it is below 3.11, run step 2 again and make sure the line begins with (surveys).

Git is missing

ImportError: Failed to initialize: Bad git executable.

The library cannot even start without git. It is installed in step 2, so this points to the same cause: the surveys environment is not active. Run conda activate surveys and try again.

It works on the server but not on my own machine

the cell never finishes, or fails only on the last line

The server has both the survey data and the tools already in place, so it is not a fair comparison. On your own machine the last line is the one that does real work, and it can fail for two quite different reasons: it may simply still be downloading, or it may have stopped with NoCredentialsError, which almost always means no World Bank API key is configured. Check whether the cell still shows [*], and read the last line of any red traceback.

No World Bank API key

botocore.exceptions.NoCredentialsError: Unable to locate credentials

The message names the wrong thing. It nearly always means no World Bank Microdata API key is configured, so the library will not unlock the data it ships with. Get a key (see below), and either re-run the installer script or save it by hand.

A likely second cause, if you are sure you have a key: it was written to the wrong place. The config file lives where lsms_library.config._config_file() says it does, and that differs by platform. Run those two lines in a notebook and check the file is actually there.

This used to be a GnuPG problem. It is not any more.

Older versions needed the gpg binary, which macOS and Windows do not ship — hence the Gpg4win step that used to be on this page. Since LSMS_Library 0.10 the decryption is pure Python, and gpg is only a fallback for old wheels. If you installed Gpg4win for this workshop you can leave it or remove it; nothing here needs it.

You need a World Bank Microdata key

It is free and takes a few minutes: microdata.worldbank.org. Register, then accept the terms of use for the LSMS collections.

The key is how your access is meant to be authorised. The surveys belong to national statistical offices and the World Bank, whose terms ask each user to accept them individually; holding your own key is how you do that. It is also what lets the library download from the World Bank directly, which is what you will need for anything the shared cache does not already carry — and the cache carries a fraction of the roughly forty countries the library covers.

You may find some data loads before you have entered a key. Do not read that as permission: it is the shared read cache answering, and the terms you are working under are still the Bank's. Get the key.

Getting the key, step by step

The World Bank publishes no instructions for this, so follow these closely — the key is not where you would expect, and it is shown only once.

  1. Register

    microdata.worldbank.org/auth/register. Give your name, email, institution and a password.

  2. Activate from the email they send

    You cannot log in until you click that link. Check your spam folder — this is where most people get stuck.

  3. Log in and open your profile

    Log in, then click the person icon with your name at the top right and choose Profile. Direct link: microdata.worldbank.org/index.php/auth/profile.

  4. Generate the key

    Scroll past your details to a section headed API keys. It will say “No API keys found.” Click the blue Generate API key button beside that heading.

  5. Copy it at once

    The key — 64 characters — appears in a box with the warning that it will not be shown again. That is literal. Afterwards you see only its first few characters. If you lose it, delete it and generate another.

To save the key on your own machine, paste this into a Jupyter cell, putting your key between the quotes:

import yaml
from lsms_library import config

key = "PASTE-YOUR-KEY-HERE"

dst = config._config_file()
dst.parent.mkdir(parents=True, exist_ok=True)
data = yaml.safe_load(dst.read_text()) if dst.exists() else {}
data = {**(data or {}), "microdata_api_key": key}
dst.write_text(yaml.safe_dump(data, sort_keys=True))
print("saved to", dst)

Ask the library where its config file goes rather than assuming. The location differs by platform — ~/.config on Linux, ~/Library/Application Support on macOS, and under %LOCALAPPDATA% on Windows — so a hardcoded path works on one of the three. Restart the kernel afterwards (Kernel → Restart Kernel) so the library picks it up. The installer script does all of this for you.

Three things to know

Treat the key like a password. It carries your identity and your permissions — anyone holding it acts as you. Never put it in a shared notebook, an email, or a git repository.

It expires after a year, and you may hold at most five at a time. Both are visible on the same profile page.

A bad or expired key reports LOGIN_REQUIRED, not “bad key”. If downloads start failing that way, generate a fresh one rather than assuming you are signed out.

On the workshop server none of this mattered, because the data was already there. On your own machine it is the thing that makes the library work at all.

Tell us how it went

Whether it worked or not, please send one line saying which computer you used and where you stopped. If you want to give us a more precise report, run the fuller check below instead of the five lines in step 4 — it tests each requirement separately and prints two lines to copy into your reply.

The fuller check (optional)

Paste all of this into a Jupyter cell, or save it as check_setup.py and run python check_setup.py.

"""Workshop setup check: Analysis of Household Surveys, U. of Ghana.

Run this after installing.  It checks each requirement in turn and
prints a short report.  Nothing here changes your computer; it only
looks at what is already installed and downloads one small table.

Usage:
    python check_setup.py

or paste the whole file into a Jupyter cell and run it.
"""

import platform
import shutil
import sys

RESULTS = []


def record(name, ok, detail=""):
    RESULTS.append((name, ok, detail))
    mark = "PASS" if ok else "FAIL"
    line = f"[{mark}] {name}"
    if detail:
        line += f" -- {detail}"
    print(line, flush=True)
    return ok


def main():
    print("=" * 62)
    print("Analysis of Household Surveys -- setup check")
    print("=" * 62)
    print(f"System: {platform.system()} {platform.machine()}")
    print(f"Python: {sys.version.split()[0]}")
    print("-" * 62)

    # 1. Python version -------------------------------------------------
    ver = sys.version_info
    record(
        "Python 3.11 or newer",
        ver >= (3, 11),
        f"found {ver.major}.{ver.minor}.{ver.micro}"
        + ("" if ver >= (3, 11) else "; this is too old"),
    )

    # 2. git on PATH -----------------------------------------------------
    git = shutil.which("git")
    record(
        "git is installed",
        git is not None,
        git or "not found -- the library cannot even start without it",
    )

    # 3. gpg on PATH -----------------------------------------------------
    # Informational only.  LSMS_Library decrypts its own S3 read credentials
    # in pure Python (Fernet, via `cryptography', a hard dependency) as of
    # GH #741; the bundled s3_reader_creds.enc ships in the wheel.  The gpg
    # binary -- which macOS and Windows do not ship -- is now only a fallback
    # for wheels built before that file existed.  Reporting its absence as a
    # FAIL sent people off to install Gpg4win for no reason.
    gpg = shutil.which("gpg") or shutil.which("gpg2")
    record(
        "gpg is installed (optional)",
        True,
        gpg or "not found -- fine; decryption is pure Python since LSMS_Library 0.10",
    )

    # 3b. the World Bank Microdata API key --------------------------------
    # This is the real access gate: a valid key proves acceptance of the
    # World Bank's terms of use, and is what unlocks both direct downloads
    # and the shared S3 read cache.
    try:
        from lsms_library import config as _cfg

        _key = _cfg.microdata_api_key()
        record(
            "World Bank Microdata API key",
            bool(_key),
            (f"configured ({_key[:6]}...)" if _key
             else "not set -- data access will fail; see the install page"),
        )
    except Exception as exc:  # noqa: BLE001 -- pre-import, report anything
        record("World Bank Microdata API key", False, f"could not check: {exc}")

    # 4. the library imports ---------------------------------------------
    lib_ok = False
    try:
        import lsms_library as ll

        lib_ok = record(
            "lsms_library imports",
            True,
            f"version {getattr(ll, '__version__', 'unknown')}",
        )
    except Exception as exc:  # noqa: BLE001 -- report anything to the student
        record("lsms_library imports", False, f"{type(exc).__name__}: {exc}")

    # 4b. pandas matches what THIS lsms_library asks for -------------------
    # Do not hard-code a bound. 0.9.x needs pandas < 3; 0.10.1 and later need
    # pandas > 3. Read the requirement from the installed package's own
    # metadata so this check stays right across that change instead of
    # failing a correct installation.
    try:
        import pandas as _pd
        from importlib.metadata import requires as _requires
        from packaging.requirements import Requirement as _Req
        from packaging.version import Version as _V

        _spec = None
        for _r in _requires("LSMS_Library") or []:
            _req = _Req(_r)
            if _req.name.lower() == "pandas" and not _req.marker:
                _spec = _req.specifier
                break

        if _spec is None:
            record("pandas version suits the library", True,
                   f"pandas {_pd.__version__}; library states no bound")
        else:
            _ok = _V(_pd.__version__) in _spec
            record(
                "pandas version suits the library",
                _ok,
                f"pandas {_pd.__version__}, library wants pandas{_spec}"
                + ("" if _ok else '; run: pip install --upgrade LSMS_Library'),
            )
    except Exception as exc:  # noqa: BLE001
        record("pandas version suits the library", False,
               f"{type(exc).__name__}: {exc}")

    # 5. Jupyter ----------------------------------------------------------
    jupyter_exe = shutil.which("jupyter")
    jupyter_mod = False
    for mod in ("jupyterlab", "notebook", "jupyter_server"):
        try:
            __import__(mod)
            jupyter_mod = True
            break
        except Exception:  # noqa: BLE001
            continue
    record(
        "Jupyter is installed",
        bool(jupyter_exe or jupyter_mod),
        (jupyter_exe or "found as a Python package")
        if (jupyter_exe or jupyter_mod)
        else "not found -- run: pip install jupyterlab",
    )

    # 6. real data downloads ----------------------------------------------
    if lib_ok:
        print("-" * 62)
        print("Downloading a table from the Ghana Living Standards Survey.")
        print("This is the slow step -- one to several minutes on a first")
        print("run, depending on your connection.  Later runs are fast,")
        print("because the data is cached on your machine.")
        print("-" * 62, flush=True)
        try:
            import lsms_library as ll

            ghana = ll.Country("GhanaLSS")
            waves = ghana.waves
            record("Ghana survey waves found", bool(waves), f"{len(waves)} waves")
            roster = ghana.household_roster()
            record(
                "Ghana data downloads",
                len(roster) > 0,
                f"{len(roster):,} rows of household roster data",
            )
        except Exception as exc:  # noqa: BLE001
            record("Ghana data downloads", False, f"{type(exc).__name__}: {exc}")

    # Report ---------------------------------------------------------------
    print("=" * 62)
    failed = [name for name, ok, _ in RESULTS if not ok]
    if not failed:
        print("ALL CHECKS PASSED.  You are ready for the workshop.")
    else:
        print("SOME CHECKS FAILED:")
        for name in failed:
            print(f"  - {name}")
        print()
        print("This is normal and it is not a problem.  The workshop runs")
        print("on a shared server, so you can take part either way.")
    print()
    print("Please copy the two lines below into your reply:")
    print("-" * 62)
    print(f"REPORT {platform.system()}/{platform.machine()} py{ver.major}.{ver.minor}")
    print("REPORT " + ("all-passed" if not failed else "failed: " + ", ".join(failed)))
    print("-" * 62)


if __name__ == "__main__":
    main()