Count co-occurrences of two categorical lists into a nested dict-of-dicts (a contingency table). With pandas, pd.crosstab builds this frequency table in one call, with sorted row and column labels.

By hand

Loop over both parallel lists at once by index. For each (region, status) pair, create the inner dict when a region is first seen, initialize the count to zero when a status is first seen for that region, then increment.

naive.py
Replay: real traced execution (multi-file project)
region = ['N', 'S', 'N', 'N', 'S', 'S']
status = ['A', 'B', 'B', 'A', 'A', 'B']
counts = {}
for i in range(len(region)):
    r = region[i]
    s = status[i]
    if r not in counts:
        counts[r] = {}
    if s not in counts[r]:
        counts[r][s] = 0
    counts[r][s] += 1
result = {k: dict(sorted(counts[k].items())) for k in sorted(counts)}
print('RESULT:', result)
  1. region ← ['N', 'S', 'N', 'N', 'S', 'S']

    1region = ['N', 'S', 'N', 'N', 'S', 'S']2status = ['A', 'B', 'B', 'A', 'A', 'B']
    values this step['N', 'S', 'N', 'N', 'S', 'S']region
  2. status ← ['A', 'B', 'B', 'A', 'A', 'B']

    1region = ['N', 'S', 'N', 'N', 'S', 'S']2status = ['A', 'B', 'B', 'A', 'A', 'B']3counts = {}
    values this step['A', 'B', 'B', 'A', 'A', 'B']status
  3. counts ← {}

    2status = ['A', 'B', 'B', 'A', 'A', 'B']3counts = {}4for i in range(len(region)):
    values this step{}counts
  4. i ← 0

    3counts = {}4for i in range(len(region)):5    r = region[i]
    values this step0i
  5. r ← 'N'

    4for i in range(len(region)):5    r = region[i]6    s = status[i]
    values this step'N'r
  6. s ← 'A'

    5r = region[i]6s = status[i]7if r not in counts:
    values this step'A's
  7. if r not in counts:

    6s = status[i]7if r not in counts:8    counts[r] = {}
  8. counts ← {'N': {}}

    7if r not in counts:8    counts[r] = {}9if s not in counts[r]:
    values this step{} {'N': {}}counts
  9. if s not in counts[r]:

    8    counts[r] = {}9if s not in counts[r]:10    counts[r][s] = 0
  10. counts ← {'N': {'A': 0}}

    9if s not in counts[r]:10    counts[r][s] = 011counts[r][s] += 1
    values this step{'N': {}} {'N': {'A': 0}}counts
  11. counts ← {'N': {'A': 1}}

    10        counts[r][s] = 011    counts[r][s] += 112result = {k: dict(sorted(counts[k].items())) for k in sorted(counts)}
    values this step{'N': {'A': 0}} {'N': {'A': 1}}counts
  12. i ← 1

    3counts = {}4for i in range(len(region)):5    r = region[i]
    values this step0 1i
  13. r ← 'S'

    4for i in range(len(region)):5    r = region[i]6    s = status[i]
    values this step'N' 'S'r
  14. s ← 'B'

    5r = region[i]6s = status[i]7if r not in counts:
    values this step'A' 'B's
  15. if r not in counts:

    6s = status[i]7if r not in counts:8    counts[r] = {}
  16. counts ← {'N': {'A': 1}, 'S': {}}

    7if r not in counts:8    counts[r] = {}9if s not in counts[r]:
    values this step{'N': {'A': 1}} {'N': {'A': 1}, 'S': {}}counts
  17. if s not in counts[r]:

    8    counts[r] = {}9if s not in counts[r]:10    counts[r][s] = 0
  18. counts ← {'N': {'A': 1}, 'S': {'B': 0}}

    9if s not in counts[r]:10    counts[r][s] = 011counts[r][s] += 1
    values this step{'N': {'A': 1}, 'S': {}} {'N': {'A': 1}, 'S': {'B': 0}}counts
  19. counts ← {'N': {'A': 1}, 'S': {'B': 1}}

    10        counts[r][s] = 011    counts[r][s] += 112result = {k: dict(sorted(counts[k].items())) for k in sorted(counts)}
    values this step{'N': {'A': 1}, 'S': {'B': 0}} {'N': {'A': 1}, 'S': {'B': 1}}counts
  20. i ← 2

    3counts = {}4for i in range(len(region)):5    r = region[i]
    values this step1 2i
  21. r ← 'N'

    4for i in range(len(region)):5    r = region[i]6    s = status[i]
    values this step'S' 'N'r
  22. s = status[i]

    5r = region[i]6s = status[i]7if r not in counts:
  23. if r not in counts:

    6s = status[i]7if r not in counts:8    counts[r] = {}
  24. if s not in counts[r]:

    8    counts[r] = {}9if s not in counts[r]:10    counts[r][s] = 0
  25. counts ← {'N': {'A': 1, 'B': 0}, 'S': {'B': 1}}

    9if s not in counts[r]:10    counts[r][s] = 011counts[r][s] += 1
    values this step{'N': {'A': 1}, 'S': {'B': 1}} {'N': {'A': 1, 'B': 0}, 'S': {'B': 1}}counts
  26. counts ← {'N': {'A': 1, 'B': 1}, 'S': {'B': 1}}

    10        counts[r][s] = 011    counts[r][s] += 112result = {k: dict(sorted(counts[k].items())) for k in sorted(counts)}
    values this step{'N': {'A': 1, 'B': 0}, 'S': {'B': 1}} {'N': {'A': 1, 'B': 1}, 'S': {'B': 1}}counts
  27. i ← 3

    3counts = {}4for i in range(len(region)):5    r = region[i]
    values this step2 3i
  28. r = region[i]

    4for i in range(len(region)):5    r = region[i]6    s = status[i]
  29. s ← 'A'

    5r = region[i]6s = status[i]7if r not in counts:
    values this step'B' 'A's
  30. if r not in counts:

    6s = status[i]7if r not in counts:8    counts[r] = {}
  31. if s not in counts[r]:

    8    counts[r] = {}9if s not in counts[r]:10    counts[r][s] = 0
  32. counts ← {'N': {'A': 2, 'B': 1}, 'S': {'B': 1}}

    10        counts[r][s] = 011    counts[r][s] += 112result = {k: dict(sorted(counts[k].items())) for k in sorted(counts)}
    values this step{'N': {'A': 1, 'B': 1}, 'S': {'B': 1}} {'N': {'A': 2, 'B': 1}, 'S': {'B': 1}}counts
  33. i ← 4

    3counts = {}4for i in range(len(region)):5    r = region[i]
    values this step3 4i
  34. r ← 'S'

    4for i in range(len(region)):5    r = region[i]6    s = status[i]
    values this step'N' 'S'r
  35. s = status[i]

    5r = region[i]6s = status[i]7if r not in counts:
  36. if r not in counts:

    6s = status[i]7if r not in counts:8    counts[r] = {}
  37. if s not in counts[r]:

    8    counts[r] = {}9if s not in counts[r]:10    counts[r][s] = 0
  38. counts ← {'N': {'A': 2, 'B': 1}, 'S': {'B': 1, 'A': 0}}

    9if s not in counts[r]:10    counts[r][s] = 011counts[r][s] += 1
    values this step{'N': {'A': 2, 'B': 1}, 'S': {'B': 1}} {'N': {'A': 2, 'B': 1}, 'S': {'B': 1, 'A': 0}}counts
  39. counts ← {'N': {'A': 2, 'B': 1}, 'S': {'B': 1, 'A': 1}}

    10        counts[r][s] = 011    counts[r][s] += 112result = {k: dict(sorted(counts[k].items())) for k in sorted(counts)}
    values this step{'N': {'A': 2, 'B': 1}, 'S': {'B': 1, 'A': 0}} {'N': {'A': 2, 'B': 1}, 'S': {'B': 1, 'A': 1}}counts
  40. i ← 5

    3counts = {}4for i in range(len(region)):5    r = region[i]
    values this step4 5i
  41. r = region[i]

    4for i in range(len(region)):5    r = region[i]6    s = status[i]
  42. s ← 'B'

    5r = region[i]6s = status[i]7if r not in counts:
    values this step'A' 'B's
  43. if r not in counts:

    6s = status[i]7if r not in counts:8    counts[r] = {}
  44. if s not in counts[r]:

    8    counts[r] = {}9if s not in counts[r]:10    counts[r][s] = 0
  45. counts ← {'N': {'A': 2, 'B': 1}, 'S': {'B': 2, 'A': 1}}

    10        counts[r][s] = 011    counts[r][s] += 112result = {k: dict(sorted(counts[k].items())) for k in sorted(counts)}
    values this step{'N': {'A': 2, 'B': 1}, 'S': {'B': 1, 'A': 1}} {'N': {'A': 2, 'B': 1}, 'S': {'B': 2, 'A': 1}}counts
  46. for i in range(len(region)):

    3counts = {}4for i in range(len(region)):5    r = region[i]
  47. result ← {'N': {'A': 2, 'B': 1}, 'S': {'A': 1, 'B': 2}}

    11    counts[r][s] += 112result = {k: dict(sorted(counts[k].items())) for k in sorted(counts)}13print('RESULT:', result)
    values this step{'N': {'A': 2, 'B': 1}, 'S': {'A': 1, 'B': 2}}result
  48. stdout ← RESULT: {'N': {'A': 2, 'B': 1}, 'S': {'A': 1, 'B': 2}}

    12result = {k: dict(sorted(counts[k].items())) for k in sorted(counts)}13print('RESULT:', result)
    values this stepRESULT: {'N': {'A': 2, 'B': 1}, 'S': {'A': 1, 'B': 2}}stdout

With pandas

pd.crosstab(df['region'], df['status']) returns a DataFrame where each cell is the count of rows with that (region, status) combination. Row and column labels are sorted automatically.

library.py
import pandas as pd
from dalib.display import set_display
set_display()

region = ['N', 'S', 'N', 'N', 'S', 'S']
status = ['A', 'B', 'B', 'A', 'A', 'B']
df = pd.DataFrame({'region': region, 'status': status})
ct = pd.crosstab(df['region'], df['status'])
result = {r: {c: int(ct.loc[r, c]) for c in ct.columns} for r in ct.index}
print('index:', ct.index.tolist())
print('columns:', ct.columns.tolist())
print('A:', [int(ct.loc[r, 'A']) for r in ct.index])
print('B:', [int(ct.loc[r, 'B']) for r in ct.index])
print('RESULT:', result)
index: ['N', 'S']
columns: ['A', 'B']
A: [2, 1]
B: [1, 2]
RESULT: {'N': {'A': 2, 'B': 1}, 'S': {'A': 1, 'B': 2}}

Implementation notes

  • pd.crosstab is a frequency-count pivot — it counts rows, not aggregates values. For value aggregation, use pivot_table with aggfunc='sum' etc.
  • Pass normalize='index' to get row-fraction proportions (each row sums to 1), normalize='columns' for column fractions, or normalize='all' (same as normalize=True) to normalize over all cells.
  • Cross-reference: groupby-count (ch04) for single-column frequency counts; pivot-table-simple (this chapter) for value aggregation into a grid.