LSMS_Library 0.2.7__py3-none-any.whl → 0.2.10.dev0__py3-none-any.whl

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
Files changed (55) hide show
  1. lsms_library/__init__.py +8 -2
  2. lsms_library/countries/.dvc/.gitignore +3 -0
  3. lsms_library/countries/.dvc/config +13 -0
  4. lsms_library/countries/.dvc/plots/confusion.json +30 -0
  5. lsms_library/countries/.dvc/plots/default.json +29 -0
  6. lsms_library/countries/.dvc/plots/scatter.json +27 -0
  7. lsms_library/countries/.dvc/plots/smooth.json +39 -0
  8. lsms_library/countries/.dvc/s3_config +2 -0
  9. lsms_library/countries/.dvc/s3_reader_creds.gpg +0 -0
  10. lsms_library/countries/Bosnia-Herzegovina/2003/Data/cbh_ind_weighs.dta.dvc +5 -0
  11. lsms_library/countries/Bosnia-Herzegovina/2003/Data/cbhcfhh.dta.dvc +5 -0
  12. lsms_library/countries/Bosnia-Herzegovina/2003/Data/cbhcfind.dta.dvc +5 -0
  13. lsms_library/countries/Bosnia-Herzegovina/2003/Data/cbhhhpos.dta.dvc +5 -0
  14. lsms_library/countries/Bosnia-Herzegovina/2003/Data/cbhihh.dta.dvc +5 -0
  15. lsms_library/countries/Bosnia-Herzegovina/2003/Data/cbhiind.dta.dvc +5 -0
  16. lsms_library/countries/Bosnia-Herzegovina/2003/Data/cbhjhist.dta.dvc +5 -0
  17. lsms_library/countries/China/1995-97/_/data_info.yml +28 -0
  18. lsms_library/countries/China/_/data_scheme.yml +9 -0
  19. lsms_library/countries/India/1997-98/_/data_info.yml +29 -0
  20. lsms_library/countries/India/1997-98/_/testing.py +9 -0
  21. lsms_library/countries/India/_/data_scheme.yml +13 -0
  22. lsms_library/countries/Iraq/2006-07/_/data_info.yml +13 -0
  23. lsms_library/countries/Iraq/2012/_/data_info.yml +13 -0
  24. lsms_library/countries/Iraq/_/data_scheme.yml +8 -0
  25. lsms_library/countries/Kosovo/2000/_/data_info.yml +14 -0
  26. lsms_library/countries/Kosovo/_/data_scheme.yml +9 -0
  27. lsms_library/countries/Liberia/2018-19/_/data_info.yml +13 -0
  28. lsms_library/countries/Liberia/_/data_scheme.yml +8 -0
  29. lsms_library/countries/Nigeria/_/household_roster.py +0 -2
  30. lsms_library/countries/Serbia/2007/_/data_info.yml +13 -0
  31. lsms_library/countries/Serbia/_/data_scheme.yml +8 -0
  32. lsms_library/countries/Serbia and Montenegro/2002/_/data_info.yml +13 -0
  33. lsms_library/countries/Serbia and Montenegro/2003/_/data_info.yml +13 -0
  34. lsms_library/countries/Serbia and Montenegro/_/data_scheme.yml +8 -0
  35. lsms_library/countries/South Africa/1993/_/data_info.yml +34 -0
  36. lsms_library/countries/South Africa/_/data_scheme.yml +8 -0
  37. lsms_library/countries/Timor-Leste/2007-08/_/data_info.yml +13 -0
  38. lsms_library/countries/Timor-Leste/_/data_scheme.yml +8 -0
  39. lsms_library/countries/Togo/2018/_/data_info.yml +13 -0
  40. lsms_library/countries/Togo/_/data_scheme.yml +8 -0
  41. lsms_library/countries/Uganda/2005-06/_/data_info.yml +12 -1
  42. lsms_library/countries/Uganda/2009-10/_/data_info.yml +10 -0
  43. lsms_library/countries/Uganda/_/Makefile +2 -2
  44. lsms_library/countries/Uganda/_/data_scheme.yml +9 -0
  45. lsms_library/countries/Uganda/_/household_characteristics.py +2 -4
  46. lsms_library/countries/Uganda/_/unitlabels.csv +290 -0
  47. lsms_library/country.py +6 -7
  48. lsms_library/dvc_permissions.py +68 -0
  49. lsms_library/local_tools.py +3 -3
  50. lsms_library-0.2.10.dev0.dist-info/METADATA +290 -0
  51. {lsms_library-0.2.7.dist-info → lsms_library-0.2.10.dev0.dist-info}/RECORD +53 -14
  52. lsms_library/contributing.org +0 -290
  53. lsms_library-0.2.7.dist-info/METADATA +0 -138
  54. {lsms_library-0.2.7.dist-info → lsms_library-0.2.10.dev0.dist-info}/LICENSE.txt +0 -0
  55. {lsms_library-0.2.7.dist-info → lsms_library-0.2.10.dev0.dist-info}/WHEEL +0 -0
@@ -0,0 +1,290 @@
1
+ Code,Preferred Label
2
+ 0,---
3
+ 1,Kg
4
+ 2,Gram
5
+ 3,Litre
6
+ 4,Akendo (Small)
7
+ 5,Metre
8
+ 6,Square metre
9
+ 7,Yard
10
+ 8,Millilitre
11
+ 9,Sack (120 kgs)
12
+ 10,Sack (100 kgs)
13
+ 11,Sack (80 kgs)
14
+ 12,Sack (50 kgs)
15
+ 13,Sack (unspecified)
16
+ 14,Jerrican (20 lts)
17
+ 15,Jerrican (10 lts)
18
+ 16,Jerrican (5 lts)
19
+ 17,Jerrican (3 lts)
20
+ 18,Jerrican (2 lts)
21
+ 19,Jerrican (1 lt)
22
+ 20,Tin (Debe) (20 lts)
23
+ 21,Tin (5 lts)
24
+ 22,Plastic Basin (15 lts)
25
+ 23,Bottle(750ml)
26
+ 24,Bottle(500ml)
27
+ 25,Bottle(350ml)
28
+ 26,Bottle(300ml)
29
+ 27,Bottle(250ml)
30
+ 28,Bottle(150ml)
31
+ 29,Kimbo/Cowboy/Blueband Tin (2kg)
32
+ 30,Kimbo/Cowboy/Blueband Tin (1kg)
33
+ 31,Kimbo/Cowboy/Blueband Tin (0.5kg)
34
+ 32,Cup/Mug(0.5lt)
35
+ 33,Glass(0.25lt)
36
+ 34,Ladle(100g)
37
+ 35,Table spoon
38
+ 36,Tea spoon
39
+ 37,Basket (20 kg)
40
+ 38,Basket (10 kg)
41
+ 39,Basket (5 kg)
42
+ 40,Basket (2 kg)
43
+ 41,Loaf (1kg)
44
+ 42,Loaf (500g)
45
+ 43,Buns (200 g)
46
+ 44,Buns (100 g)
47
+ 45,Buns (50 g)
48
+ 46,Bathing soap (Tablet)
49
+ 47,Washing soap (Bar)
50
+ 48,Washing soap (Tablet)
51
+ 49,Packet (2 kg)
52
+ 50,Packet (1 kg)
53
+ 51,Packet (500 g)
54
+ 52,Packet (250 g)
55
+ 53,Packet (100 g)
56
+ 54,Packet(125g)
57
+ 55,Fish Whole (Small)
58
+ 56,Fish Whole (Medium)
59
+ 57,Fish Whole (Large)
60
+ 58,Fish Cut piece (up to 1kg)
61
+ 59,Fish Cut piece (1 - 2 kg)
62
+ 60,Fish Cut piece(Above 2 kg)
63
+ 61,Tray of 30 eggs
64
+ 62,Ream
65
+ 63,Crate
66
+ 64,Heap (unspecified)
67
+ 65,Dozen
68
+ 66,Bundle (Unspecified)
69
+ 67,Bunch (Big)
70
+ 68,Bunch (Medium)
71
+ 69,Bunch (Small)
72
+ 70,Bunch (Big)
73
+ 71,Bunch (Medium)
74
+ 72,Bunch (Small)
75
+ 73,Cluster (Unspecified)
76
+ 74,Gourd (1-5lts)
77
+ 75,Gourd (5-10lts)
78
+ 76,Gourd (Above 10 lts)
79
+ 77,Jug (2 lts)
80
+ 78,Jug (1.5 lts)
81
+ 79,Jug (1 lt)
82
+ 80,Tot (50 ml)
83
+ 81,Tot (sachet)
84
+ 82,Tot (Unspecified)
85
+ 83,Tobacco leaf (Number)
86
+ 84,Pair
87
+ 85,Number of Units (General)
88
+ 86,Acre
89
+ 87,Piece (Big)
90
+ 88,Piece (Medium)
91
+ 89,Piece (Small)
92
+ 90,Heap (Big)
93
+ 91,Heap (Medium)
94
+ 92,Heap (Small)
95
+ 93,Cluster (Big)
96
+ 94,Cluster (Medium)
97
+ 95,Cluster (Small)
98
+ 96,Bundle (Big)
99
+ 97,Bundle (Medium)
100
+ 98,Bundle (Small)
101
+ 99,Others specify
102
+ 100,Fish Whole (Large)
103
+ 101,Fish Whole (Medium)
104
+ 102,Fish Whole (Small)
105
+ 103,Plastic Basin (5 ltrs)
106
+ 104,Glass (500ml)
107
+ 105,Glass (125ml)
108
+ 106,Jug (2.5ltrs)
109
+ 107,Nice cup (100g) - Large
110
+ 108,Nice cup (60g) - Medium
111
+ 109,Nice cup (50g) - Small
112
+ 110,Metallic tumbler (100g) - Big
113
+ 111,Metallic tumbler (50g) - Small
114
+ 112,Plastic tumbler (50g) - Big
115
+ 113,Plastic tumbler (30g) - Small
116
+ 114,Plastic plate (60g) - Large
117
+ 115,Plastic plate (30g) - Small
118
+ 116,Metallic plate (100g) - Large
119
+ 117,Metallic plate (80g) - Small
120
+ 118,Plastic bowl (40g)
121
+ 119,Nomi Tin (1kg)
122
+ 120,Nomi Tin (500g)
123
+ 121,Nomi Tin (250g)
124
+ 122,Nido Tin (400g)
125
+ 123,Akendo (Big)
126
+ 124,Akendo (Medium)
127
+ 125,Akendo (Small)
128
+ 126,Jerrican (500 ml)
129
+ 127,Container (Big)
130
+ 128,Container (Medium)
131
+ 129,Container (Small)
132
+ 130,Container (Smallest)
133
+ 131,Bottle (500g)
134
+ 132,Bottle (350g)
135
+ 133,Sadolin Tin (3ltrs)
136
+ 134,Loaf (1kg)
137
+ 135,Loaf (500g)
138
+ 137,Bottle (1ltr)
139
+ 138,Bottle (1.5ltr)
140
+ 139,Bottle (2ltr)
141
+ 140,Sacket (10g)
142
+ 141,Sacket (15g)
143
+ 142,Sacket (20g)
144
+ 143,Sacket (25g)
145
+ 144,Sacket (30g)
146
+ 145,Sacket (50g)
147
+ 146,Sacket (100g)
148
+ 147,Sacket (150g)
149
+ 104002,---
150
+ 104003,---
151
+ 104087,---
152
+ 104088,---
153
+ 104089,---
154
+ 105091,---
155
+ 105092,---
156
+ 107087,---
157
+ 107088,---
158
+ 107089,---
159
+ 107090,---
160
+ 107091,---
161
+ 107092,---
162
+ 109087,---
163
+ 109088,---
164
+ 109089,---
165
+ 109090,---
166
+ 109092,---
167
+ 111087,---
168
+ 111088,---
169
+ 111089,---
170
+ 111090,---
171
+ 113087,---
172
+ 113088,---
173
+ 113089,---
174
+ 114087,---
175
+ 114089,---
176
+ 121087,---
177
+ 121089,---
178
+ 122055,---
179
+ 122056,---
180
+ 122057,---
181
+ 123055,---
182
+ 123056,---
183
+ 123057,---
184
+ 123058,---
185
+ 123059,---
186
+ 123060,---
187
+ 123090,---
188
+ 123091,---
189
+ 123092,---
190
+ 124088,---
191
+ 125050,---
192
+ 125051,---
193
+ 125052,---
194
+ 128004,---
195
+ 128005,---
196
+ 128051,---
197
+ 128052,---
198
+ 128053,---
199
+ 129001,---
200
+ 129002,---
201
+ 129003,---
202
+ 129004,---
203
+ 130090,---
204
+ 130091,---
205
+ 130092,---
206
+ 131093,---
207
+ 131094,---
208
+ 131095,---
209
+ 132087,---
210
+ 132088,---
211
+ 132089,---
212
+ 133087,---
213
+ 133088,---
214
+ 133089,---
215
+ 133090,---
216
+ 133092,---
217
+ 135087,---
218
+ 135088,---
219
+ 135089,---
220
+ 135090,---
221
+ 135091,---
222
+ 135092,---
223
+ 135096,---
224
+ 135097,---
225
+ 135098,---
226
+ 136087,---
227
+ 136088,---
228
+ 136089,---
229
+ 136090,---
230
+ 136091,---
231
+ 136092,---
232
+ 137087,---
233
+ 137088,---
234
+ 137089,---
235
+ 138096,---
236
+ 138097,---
237
+ 138098,---
238
+ 151024,---
239
+ 151026,---
240
+ 152024,---
241
+ 153001,---
242
+ 154001,---
243
+ 155054,---
244
+ 155099,---
245
+ 157001,---
246
+ 158024,---
247
+ 158026,---
248
+ 159024,---
249
+ 160001,---
250
+ 161001,---
251
+ 164087,---
252
+ 164088,---
253
+ 164089,---
254
+ 165087,---
255
+ 165088,---
256
+ 165089,---
257
+ 166087,---
258
+ 166088,---
259
+ 166089,---
260
+ 167087,---
261
+ 167088,---
262
+ 167089,---
263
+ 167090,---
264
+ 167091,---
265
+ 167092,---
266
+ 168087,---
267
+ 168088,---
268
+ 168089,---
269
+ 168090,---
270
+ 168091,---
271
+ 168092,---
272
+ 169087,---
273
+ 169088,---
274
+ 169089,---
275
+ 170087,---
276
+ 170088,---
277
+ 170089,---
278
+ 171087,---
279
+ 171088,---
280
+ 171089,---
281
+ 807087,---
282
+ 807089,---
283
+ 136,---
284
+ 156087,---
285
+ 139093,---
286
+ 142092,---
287
+ 156096,---
288
+ 156090,---
289
+ 181087,---
290
+ 142090,---
lsms_library/country.py CHANGED
@@ -43,9 +43,8 @@ class Wave:
43
43
 
44
44
  @property
45
45
  def file_path(self):
46
- var = files("lsms_library") / "countries" / self.folder
47
- return var
48
-
46
+ return files("lsms_library") / "countries" / self.folder
47
+
49
48
  @property
50
49
  def resources(self):
51
50
  """Load the data_info.yml that describes table structure, merges, etc."""
@@ -212,7 +211,7 @@ class Wave:
212
211
  df_edit_function = mapping_info.pop('df_edit')
213
212
  dfs = []
214
213
  for file, mappings in mapping_info.items():
215
- df = df_data_grabber(f'lsms_library/countries/{self.folder}/Data/{file}', mappings['idxvars'], **mappings['myvars'], convert_categoricals=convert_cat)
214
+ df = df_data_grabber(f'./{self.folder}/Data/{file}', mappings['idxvars'], **mappings['myvars'], convert_categoricals=convert_cat)
216
215
  df = check_adding_t(df)
217
216
  # Oddity with large number for missing code
218
217
  na = df.select_dtypes(exclude=['object', 'datetime64[ns]']).max().max()
@@ -266,7 +265,7 @@ class Wave:
266
265
 
267
266
  cwd_path = self.file_path.parent / "_"
268
267
  relative_parquet_path = parquet_fn.relative_to(cwd_path.parent) # Convert to relative path
269
- subprocess.run(["make", '../' + str(relative_parquet_path)], cwd=cwd_path, check=True)
268
+ subprocess.run(["make", "-s", '../' + str(relative_parquet_path)], cwd=cwd_path, check=True)
270
269
  print(f"Makefile executed successfully for {self.name}. Rechecking for parquet file...",file=stderr)
271
270
 
272
271
  if not parquet_fn.exists():
@@ -331,7 +330,7 @@ class Country:
331
330
  # 'nonfood_expenditures.parquet', 'enterprise_income.parquet', 'assets.parquet',
332
331
  # 'earnings.parquet', 'housing.parquet', 'income.parquet', 'fct.parquet', 'nutrition.parquet']
333
332
 
334
- def __init__(self,country_name, preload_panel_ids = True):
333
+ def __init__(self, country_name, preload_panel_ids=True, verbose=False):
335
334
  self.name = country_name
336
335
  self._panel_ids_cache = None
337
336
  self._updated_ids_cache = None
@@ -510,7 +509,7 @@ class Country:
510
509
  relative_path = target_path.relative_to(cwd_path.parent)
511
510
  make_target = '../' + str(relative_path)
512
511
 
513
- subprocess.run(["make", make_target], cwd=cwd_path, check=True)
512
+ subprocess.run(["make", "-s", make_target], cwd=cwd_path, check=True)
514
513
  print(f"Makefile executed successfully for {self.name}. Rechecking for {target_path.name}...",file=stderr)
515
514
 
516
515
  if not target_path.exists():
@@ -0,0 +1,68 @@
1
+ #!/usr/bin/env python3
2
+ import git
3
+ from pathlib import Path
4
+ import getpass
5
+ import pkgutil
6
+ import gnupg
7
+
8
+ def is_git_repo(path='.'):
9
+ """
10
+ Check if the specified path is a Git repository.
11
+
12
+ Parameters:
13
+ path (str): The path to check. Default is the current directory.
14
+
15
+ Returns:
16
+ bool: True if the path is a valid Git repository, False otherwise.
17
+ """
18
+ try:
19
+ _ = git.Repo(path).git_dir
20
+ return True
21
+ except git.exc.InvalidGitRepositoryError:
22
+ return False
23
+
24
+ def authenticate(gpg_key_file='s3_reader_creds.gpg'):
25
+ """
26
+ Decrypt the specified GPG file and store the decrypted credentials securely.
27
+
28
+ This function prompts the user for a passphrase to decrypt the provided GPG
29
+ file.
30
+
31
+ Parameters:
32
+ gpg_key_file (str): The name of the GPG file containing the encrypted credentials.
33
+ Default is 's3_reader_creds.gpg'.
34
+
35
+ Raises:
36
+ ValueError: If decryption fails due to an incorrect passphrase or other issues.
37
+ """
38
+ # Construct the path to the encrypted file relative to this function’s location
39
+ gpg_path = Path(__file__).resolve().parent / 'countries' / '.dvc'
40
+ encrypted_file = gpg_path / gpg_key_file
41
+
42
+ # Load the encrypted data
43
+ encrypted_data = encrypted_file.read_bytes()
44
+
45
+ # Initialize GPG
46
+ gpg = gnupg.GPG()
47
+
48
+ # Prompt the user for the passphrase securely
49
+ passphrase = getpass.getpass(prompt='Enter passphrase for decryption: ')
50
+
51
+ # Decrypt the data
52
+ decrypted_data = gpg.decrypt(encrypted_data, passphrase=passphrase)
53
+
54
+ if decrypted_data.ok:
55
+ # Write the decrypted data to a temporary file securely
56
+ creds_file = gpg_path / 's3_creds'
57
+
58
+ if creds_file.exists():
59
+ user_input = input(f"The file {creds_file} already exists. Overwrite? (yes/no): ").strip().lower()
60
+ if user_input not in ['yes', 'y']:
61
+ print("Operation aborted. Credentials were not written.")
62
+ return
63
+
64
+ with open(creds_file, 'w') as f:
65
+ f.write(str(decrypted_data))
66
+ print(f"Decryption successful; credentials written to {creds_file}")
67
+ else:
68
+ raise ValueError("Decryption failed: check the passphrase and try again")
@@ -696,11 +696,11 @@ def panel_ids(Waves):
696
696
  if isinstance(wave_info[1], list): #tanzania
697
697
  df = wave_info[2](df, wave_info[1])
698
698
  else:
699
- df[wave_info[1]] = df[wave_info[1]].apply(format_id)
700
- df[wave_info[2]] = df[wave_info[2]].apply(format_id)
699
+ df.loc[:,wave_info[1]] = df[wave_info[1]].apply(format_id)
700
+ df.loc[:,wave_info[2]] = df[wave_info[2]].apply(format_id)
701
701
  # If a transformation function is provided (tuple length 4), apply it to the old_id column
702
702
  if len(wave_info) == 4:
703
- df[wave_info[2]] = df[wave_info[2]].apply(wave_info[3])
703
+ df.loc[:,wave_info[2]] = df[wave_info[2]].apply(wave_info[3])
704
704
  df['t'] = wave_year
705
705
  df = df.rename(columns={wave_info[1]: 'i', wave_info[2]: 'previous_i'})
706
706
  df = df.set_index(['t', 'i'])[['previous_i']]
@@ -0,0 +1,290 @@
1
+ Metadata-Version: 2.3
2
+ Name: LSMS_Library
3
+ Version: 0.2.10.dev0
4
+ Summary: Abstraction layer for Living Standards Measurement Survey data
5
+ License: CC-BY-4.0
6
+ Author: Ethan Ligon
7
+ Author-email: ligon@berkeley.edu
8
+ Requires-Python: >=3.11, <4.0
9
+ Classifier: License :: Other/Proprietary License
10
+ Classifier: Programming Language :: Python :: 3
11
+ Classifier: Programming Language :: Python :: 3.11
12
+ Classifier: Programming Language :: Python :: 3.12
13
+ Classifier: Programming Language :: Python :: 3.13
14
+ Requires-Dist: CFEDemands (>=0.7.0)
15
+ Requires-Dist: PyYAML
16
+ Requires-Dist: dvc[s3] (>=3.61.0,<4.0.0)
17
+ Requires-Dist: ligonlibrary (>=0.1.1,<0.2.0)
18
+ Requires-Dist: lsms (>=0.4.7,<0.5.0)
19
+ Requires-Dist: openai (>=1.99.5,<2.0.0)
20
+ Requires-Dist: pandas (>=2.2,<3.0)
21
+ Requires-Dist: pyarrow (>=21.0.0,<22.0.0)
22
+ Requires-Dist: pyreadstat (>=1.3.0,<2.0.0)
23
+ Requires-Dist: python-gnupg
24
+ Requires-Dist: scikit-learn (>=1.7.0,<2.0.0)
25
+ Requires-Dist: xarray (>=2025.7.1,<2026.0.0)
26
+ Description-Content-Type: text/plain
27
+
28
+ #+TITLE: LSMS_Library
29
+ #+AUTHOR: Ethan Ligon
30
+ #+OPTIONS: toc:nil
31
+
32
+ [[https://doi.org/10.5281/zenodo.17258079][https://zenodo.org/badge/796958546.svg]]
33
+
34
+ A Python library providing a uniform interface to Living Standards Measurement Study (LSMS) household surveys from multiple countries and years, without the data loss typical of traditional harmonization approaches.
35
+
36
+ * The Problem
37
+
38
+ LSMS datasets are invaluable for studying poverty, consumption, and household welfare across developing countries. However, each country's survey uses different:
39
+ - Variable names and encodings
40
+ - Food classification systems
41
+ - Questionnaire structures
42
+ - File formats and organization
43
+
44
+ Researchers typically spend weeks learning each new dataset's idiosyncrasies or use pre-harmonized datasets that sacrifice detail and comparability. Cross-country or longitudinal analyses become prohibitively time-consuming.
45
+
46
+ * The Solution
47
+
48
+ LSMS_Library provides an *abstraction layer* that gives you a consistent interface to work with any supported LSMS dataset. Instead of harmonizing the data itself (which loses information), we harmonize the /way you access/ the data.
49
+
50
+ This means you can:
51
+ - Write analysis code once and apply it to multiple countries/years
52
+ - Switch between datasets without rewriting your code
53
+ - Preserve the full detail and structure of the original surveys
54
+ - Extend support to new surveys by writing simple YAML configuration files
55
+
56
+ * Quick Start
57
+
58
+ #+begin_src python
59
+ import lsms_library as ll
60
+
61
+ # Load a country's LSMS data
62
+ uga = ll.Country('Uganda')
63
+
64
+ # See available survey waves
65
+ print(uga.waves)
66
+ # ['2005-06', '2009-10', '2010-11', '2011-12', '2013-14', '2015-16', '2018-19', '2019-20']
67
+
68
+ # See available standardized data types
69
+ print(uga.data_scheme)
70
+ # ['people_last7days', 'food_acquired', 'food_expenditures', 'food_prices',
71
+ # 'household_characteristics', 'income', 'nutrition', ...]
72
+
73
+ # Access standardized food expenditure data across all waves
74
+ food_exp = uga.food_expenditures()
75
+ # Returns a multi-indexed DataFrame with household, time, region, and food item
76
+ #+end_src
77
+
78
+ * Key Features
79
+
80
+ - *Uniform Interface*: Access variables using consistent names across countries (e.g., =food_expenditures()=, =household_characteristics()=)
81
+ - *Multi-Wave Panel Support*: For countries with panel surveys, household IDs are automatically standardized across waves, enabling longitudinal analysis without manual matching
82
+ - *Zero Data Loss*: Original data structure and detail preserved; access raw data through the same interface
83
+ - *Standardized Data Schemes*: Common data types (=food_prices=, =nutrition=, =income=, etc.) mapped across all countries
84
+ - *DVC Integration*: Stream data from remote storage without filling your disk
85
+ - *Extensible*: Add new surveys by creating YAML configuration files (no Python required)
86
+ - *Multiple Countries*: Supports LSMS surveys from Nigeria, Tanzania, Uganda, Ethiopia, Malawi, and more
87
+
88
+ * Installation
89
+
90
+ #+begin_src bash
91
+ pip install LSMS_Library
92
+ #+end_src
93
+
94
+ ** Data Access
95
+
96
+ The library uses DVC (Data Version Control) to manage data stored in remote S3 buckets. To access the data, you'll need credentials:
97
+
98
+ - *Read access*: Contact ligon@berkeley.edu for read-only credentials to access the data
99
+ - *Write access (contributors)*: To contribute new datasets, contact ligon@berkeley.edu for write credentials. You'll need to establish [[https://docs.github.com/en/authentication/managing-commit-signature-verification/generating-a-new-gpg-key][GPG/PGP credentials]] for secure access.
100
+
101
+ Once you have credentials, the library will handle data streaming automatically.
102
+
103
+ * Usage Examples
104
+
105
+ ** Working with Food Consumption Data
106
+
107
+ #+begin_src python
108
+ import lsms_library as ll
109
+
110
+ # Load country data
111
+ uga = ll.Country('Uganda')
112
+ tza = ll.Country('Tanzania')
113
+
114
+ # Access food expenditure data with consistent structure
115
+ uga_food = uga.food_expenditures()
116
+ tza_food = tza.food_expenditures()
117
+
118
+ # Both return DataFrames with the same multi-index structure:
119
+ # Index: (household_id, time, region, food_item)
120
+ # Even though the original surveys have completely different formats
121
+
122
+ # Access other standardized data types
123
+ prices = uga.food_prices()
124
+ nutrition = uga.nutrition()
125
+ income = uga.income()
126
+ #+end_src
127
+
128
+ ** Cross-Country Comparison
129
+
130
+ #+begin_src python
131
+ import lsms_library as ll
132
+ import pandas as pd
133
+
134
+ # Load multiple countries
135
+ countries = {
136
+ 'Uganda': ll.Country('Uganda'),
137
+ 'Tanzania': ll.Country('Tanzania'),
138
+ 'Nigeria': ll.Country('Nigeria')
139
+ }
140
+
141
+ # Collect food expenditure data from all countries
142
+ expenditure_data = {}
143
+ for name, country in countries.items():
144
+ df = country.food_expenditures()
145
+ df['country'] = name
146
+ expenditure_data[name] = df
147
+
148
+ # Combine into a single DataFrame for analysis
149
+ combined = pd.concat(expenditure_data.values(), ignore_index=False)
150
+
151
+ # Now you can analyze across countries with consistent variable names
152
+ # e.g., compare rice prices, consumption patterns, etc.
153
+ #+end_src
154
+
155
+ ** Panel Data Analysis
156
+
157
+ For countries with panel surveys, household IDs are already harmonized across waves:
158
+
159
+ #+begin_src python
160
+ import lsms_library as ll
161
+
162
+ # Load a country with panel data
163
+ uga = ll.Country('Uganda')
164
+
165
+ # Get food expenditures across all waves
166
+ food_exp = uga.food_expenditures()
167
+
168
+ # The multi-index includes time (wave), so you can track households over time
169
+ # Index levels: (household_id, time, region, food_item)
170
+
171
+ # Example: Track a specific household across waves
172
+ household_id = '00c9353d8ebe42faabf5919b81d7fae7'
173
+ household_over_time = food_exp.xs(household_id, level='i')
174
+
175
+ # Or analyze changes between specific waves
176
+ wave_2015 = food_exp.xs('2015-16', level='t')
177
+ wave_2019 = food_exp.xs('2019-20', level='t')
178
+
179
+ # Check panel structure and attrition patterns
180
+ panel_structure = ll.local_tools.panel_attrition(
181
+ uga.household_characteristics(),
182
+ uga.waves
183
+ )
184
+ # Returns a matrix showing number of households appearing in each wave pair:
185
+ # 2005-06 2009-10 2010-11 2011-12 2013-14 2015-16 2018-19 2019-20
186
+ # 2005-06 3122 2606 2386 2363 1566 1470 1358 1290
187
+ # 2009-10 NaN 2974 2617 2581 1685 1578 1454 1379
188
+ # ...
189
+ # Diagonal shows total households per wave; off-diagonal shows panel overlap
190
+ #+end_src
191
+
192
+ ** Exploring Available Data
193
+
194
+ #+begin_src python
195
+ import lsms_library as ll
196
+
197
+ uga = ll.Country('Uganda')
198
+
199
+ # See all available survey waves
200
+ print(uga.waves)
201
+ # ['2005-06', '2009-10', '2010-11', '2011-12', '2013-14', '2015-16', '2018-19', '2019-20']
202
+
203
+ # See all standardized data types available
204
+ print(uga.data_scheme)
205
+ # ['people_last7days', 'cluster_features', 'shocks', 'earnings',
206
+ # 'food_acquired', 'nutrition', 'household_characteristics',
207
+ # 'food_quantities', 'food_expenditures', 'food_prices',
208
+ # 'panel_ids', 'income', 'enterprise_income', 'other_features']
209
+
210
+ # Access any standardized data type using the same pattern
211
+ household_chars = uga.household_characteristics()
212
+ shocks = uga.shocks()
213
+ earnings = uga.earnings()
214
+ #+end_src
215
+
216
+ * Available Datasets
217
+
218
+ The library currently supports LSMS surveys from:
219
+ - *Ethiopia*: Multiple waves from the LSMS-ISA program
220
+ - *Malawi*: Multiple waves including panel data
221
+ - *Nigeria*: GHS-Panel surveys
222
+ - *Tanzania*: NPS surveys
223
+ - *Uganda*: UNPS surveys
224
+ - And more...
225
+
226
+ For a complete list of available surveys, see the country directories in the repository.
227
+
228
+ * Adding New Surveys
229
+
230
+ Adding a new LSMS survey requires no Python programming—just create YAML configuration files that map the survey's variables to the standardized interface. See [[file:CONTRIBUTING.org][CONTRIBUTING.org]] for detailed instructions.
231
+
232
+ Brief overview:
233
+ 1. Create directory structure: =Country/Year/Documentation= and =Country/Year/Data=
234
+ 2. Add source data using DVC
235
+ 3. Create YAML files mapping variables to standard names
236
+ 4. Submit a pull request
237
+
238
+ * Documentation
239
+
240
+ - *Food Classification*: Food items are standardized for spelling and format within each country. Note that food categories differ significantly across countries (e.g., what constitutes "Beans" in Uganda may not match Tanzania's classification), so cross-country food comparisons should be done carefully.
241
+ - *Variable Mappings*: YAML files in each survey directory show how local variables map to standard names
242
+ - *Panel IDs*: For countries with panel surveys, household identifiers are harmonized automatically across waves
243
+ - *API Reference*: [Coming soon]
244
+
245
+ * Contributing
246
+
247
+ We welcome contributions! Whether you're:
248
+ - Adding new survey datasets
249
+ - Improving variable mappings
250
+ - Fixing bugs
251
+ - Improving documentation
252
+
253
+ See [[file:CONTRIBUTING.org][CONTRIBUTING.org]] for detailed guidelines on adding new datasets using DVC.
254
+
255
+ * Citation
256
+
257
+ If you use LSMS_Library in your research, please cite:
258
+
259
+ #+begin_src bibtex
260
+ @software{ligon25:lsms_library,
261
+ author = {Ethan Ligon},
262
+ title = {{\tt LSMS_Library}: Abstraction layer for working with Living Standards Measurement Surveys},
263
+ year = 2025,
264
+ doi = {10.5281/zenodo.17258079},
265
+ url = {https://pypi.org/project/lsms_library/}
266
+ }
267
+ #+end_src
268
+
269
+ * License
270
+
271
+ See the [[file:LICENSE][LICENSE]] file in the repository for details.
272
+
273
+ * Contact
274
+
275
+ For questions, issues, or collaboration:
276
+ - *Data Access*: Email ligon@berkeley.edu for read or write credentials
277
+ - *GitHub Issues*: Report bugs or request features at the repository
278
+ - *Contributing*: Contact ligon@berkeley.edu to discuss contributions (GPG/PGP credentials required for write access)
279
+
280
+ * Acknowledgments
281
+
282
+ This project builds on data collection efforts by:
283
+ - The World Bank's Living Standards Measurement Study (LSMS) team
284
+ - National statistical offices in participating countries
285
+ - The LSMS-ISA initiative
286
+
287
+ ---
288
+
289
+ *Note*: This library is under active development. APIs may change as we refine the abstraction layer based on user feedback.
290
+