Title:
Data cleaning for the DRC’s Mobility Tracking report (Part 1)
Objective:
Predominately working with the Democratic Republic of the Congo team as a Data and Reporting Analyst, it falls to me to provide all of the analysis in their regular reports. This is Part 1 of 3, where I will be showcasing some of the python code used for their Mobility Tracking reports.
Methods:
Python, Data Visualisation
Explanation
Predominately working with the Democratic Republic of the Congo Team as a Data and Reporting Analyst, it fell to me to provide all of the analysis in their regular reports. One of their most important reports is their Mobility Tracking Assessments, which track the intentions, needs and movements of internally displaced people and returnees for the four eastern provinces. This article is about how I use Python to verify and output the analysis needed for the North Kivu Mobility Tracking report – you can find a link to the full report and data here.
Verifying the data
#Load in all the data
#Current Round's Data - adjust Sheet Names if needed
df_locations = pd.read_excel(r'Real Test\Nord_Kivu_MT.xlsx', sheet_name='VILLAGES')
df_cm = pd.read_excel(r'Real Test\Nord_Kivu_MT.xlsx', sheet_name='SITE_CCCM')
df_ncm = pd.read_excel(r'Real Test\Nord_Kivu_MT.xlsx',sheet_name='SITE_NON_CCCM')
df_grp_idp = pd.read_excel(r'Real Test\Nord_Kivu_MT.xlsx',sheet_name='GRP_IDP')
df_grp_ret = pd.read_excel(r'Real Test\Nord_Kivu_MT.xlsx',sheet_name='GRP_RET')
df_overview = pd.read_excel(r'Real Test\Nord_Kivu_MT.xlsx',sheet_name='APERCU')
#Data Cleaning
#Stripping leading and ending white space form titles
df_locations.columns = df_locations.columns.str.strip()
df_grp_idp.columns = df_grp_idp.columns.str.strip()
df_grp_ret.columns = df_grp_ret.columns.str.strip()
df_cm.columns = df_cm.columns.str.strip()
df_ncm.columns = df_ncm.columns.str.strip()
df_overview.columns = df_overview.columns.str.strip()
#Column Renaming
renaming_Map = {
'PROVINCE*': 'm0303_meta_adm1_label', 'PROVINCE': 'm0303_meta_adm1_label',
'Province': 'm0303_meta_adm1_label',
'CODE PROVINCE*': 'm0303_meta_adm1_pcode',
'CODE PROVINCE': 'm0303_meta_adm1_pcode',
'TERRITOIRE*': 'm0304_meta_adm2_label',
'TERRITOIRE': 'm0304_meta_adm2_label',
'Territoire': 'm0304_meta_adm2_label',
'CODE TERRITOIRE*': 'm0304_meta_adm2_pcode',
'CODE TERRITOIRE': 'm0304_meta_adm2_pcode',
'zone de santé': 'm0305_meta_adm3_label',
'ZONE DE SANTE*': 'm0305_meta_adm3_label',
'ZONE DE SANTE': 'm0305_meta_adm3_label',
'CODE ZONE DE SANTE': 'm0305_meta_adm3_pcode',
'code_ZS': 'm0305_meta_adm3_pcode',
'INDIVIDUS': 'm0310_loc_num_idp_ind',
'INDIVIDUS*': 'm0310_loc_num_idp_ind',
'Individus - IDP': 'm0310_loc_num_idp_ind',
'MENAGES' : 'm0309_loc_num_idp_hh',
'MENAGES*': 'm0309_loc_num_idp_hh',
'Menage - IDP': 'm0309_loc_num_idp_hh',
'Type' : 'site_centre',
'TYPE DE SITE*' : 'site_centre',
'type1': 'site_centre',
'TYPE DE SITE': 'site_centre',
'Hommes IDP': 'm2968_loc_num_men_idp',
'Hommes - IDP': 'm2968_loc_num_men_idp',
'HOMME': 'm2968_loc_num_men_idp',
'Femmes - IDP': 'm2969_loc_num_women_idp',
'FEMME': 'm2969_loc_num_women_idp',
'Enfants <5 - IDP': 'm2970_loc_num_children_idp',
'ENFANT': 'm2970_loc_num_children_idp'
}
df_cm = df_cm.rename(columns=lambda x: renaming_Map.get(x, x))
df_ncm = df_ncm.rename(columns=lambda x: renaming_Map.get(x, x))
#Overview Renaming Map
overview_Renaming_Map = {
'Population 2023 (Estimation de la DPS)' : 'Population 2023 (DPS)',
'Population (DPS 2023)' : 'Population 2023 (DPS)',
'Village connues' : 'Villages sur liste',
'Villages connus (DTM)' : 'Villages sur liste',
'Zone de santé' : 'Zone de sante'
}
df_overview = df_overview.rename(columns=lambda x: overview_Renaming_Map.get(x, x))
#CCCM and Non-CCCM Value Renaming Map
CCCM_Non_CCCM_value_renaming_Map = {
'Centre collectif' : 'Collective Centre',
'Site Spontané' : 'Site',
'Site Spontané ' : 'Site',
'Site Planifié' : 'Site',
'Centre Collectif' : 'Collective Centre',
'CC/ecole' : 'Collective Centre',
'CC/eglise' : 'Collective Centre',
'CC/stade' : 'Collective Centre',
'CC/terrain' : 'Collective Centre',
'CC/autre' : 'Collective Centre'
}
df_cm['site_centre'] = df_cm['site_centre'].replace(CCCM_Non_CCCM_value_renaming_Map)
df_ncm['site_centre'] = df_ncm['site_centre'].replace(CCCM_Non_CCCM_value_renaming_Map)
P-Code Example for North Kivu
#User Input
#Province
Province = 'North Kivu'
ProvinceFR = 'NORD-KIVU'
#Previous Total IDPs
previous_Total_IDPs = 1011335
#Previous Total Returnees
previous_Total_RETs = 2033653
As the administrative level becomes smaller, each P-Code has a numerical addition added to it, so it’s possible to compare sections to see if they match i.e. CD61 is the province, the highest administrative level, followed by the territory CD61 XX (where X = a unique set of numbers), followed by the health zone (or ‘zone de santé’ in French) as CD61 XX ZS YY, where YY is another unique sequence of numbers. The code below makes sure that XX and YY are correctly sequenced and then that each sequence has the correct name attached to it.
As these reports cover the 4 eastern provinces in DRC, the dictionaries contain data for all the provinces, but the above code allows the user to select which dictionary is used.
#Basic Checks Function to Check if Pcodes and Labels are Correct
def BasicChecks(df_grp_idp):
if Province == 'North Kivu':
CompD1 = {'NORD-KIVU' : 'CD61'}
CompD2 = {'BENI TERRITOIRE (OICHA)': 'CD6107',
'BENI (Ville)': 'CD6109',
'BUTEMBO': 'CD6110',
'GOMA' : 'CD6101',
'LUBERO' : 'CD6105',
'MASISI' : 'CD6103',
'NYIRAGONGO' : 'CD6102',
'RUTSHURU' : 'CD6111',
'WALIKALE' : 'CD6104'}
CompD3 = {'KALUNGUTA': 'CD6107ZS01',
'KAMANGO': 'CD6107ZS02',
'KYONDO': 'CD6107ZS03',
'MABALAKO': 'CD6107ZS04',
'MUTWANGA': 'CD6107ZS05',
'OICHA': 'CD6107ZS06',
'VUHOVI': 'CD6107ZS07',
'BENI': 'CD6109ZS01',
'BUTEMBO': 'CD6110ZS01',
'KATWA': 'CD6110ZS02',
'GOMA': 'CD6101ZS01',
'KARISIMBI': 'CD6101ZS02',
'ALIMBONGO': 'CD6105ZS01',
'BIENA': 'CD6105ZS02',
'KAYINA': 'CD6105ZS03',
'LUBERO': 'CD6105ZS04',
'MANGUREDJIPA': 'CD6105ZS05',
'MASEREKA': 'CD6105ZS06',
'MUSIENENE': 'CD6105ZS07',
'KATOYI': 'CD6103ZS01',
'KIROTSHE': 'CD6103ZS02',
'MASISI': 'CD6103ZS03',
'MWESO': 'CD6103ZS04',
'NYIRAGONGO': 'CD6102ZS01',
'BAMBO': 'CD6111ZS01',
'BINZA': 'CD6111ZS02',
'BIRAMBIZO': 'CD6111ZS03',
'KIBIRIZI': 'CD6111ZS04',
'RUTSHURU': 'CD6111ZS05',
'RWANGUBA': 'CD6111ZS06',
'ITEBERO': 'CD6104ZS01',
'KIBUA': 'CD6104ZS02',
'PINGA': 'CD6104ZS03',
'WALIKALE': 'CD6104ZS04'}
elif Province == 'Ituri':
CompD1 = {'ITURI': 'CD54'}
CompD2 = {'ARU': 'CD5409',
'DJUGU': 'CD5405',
'IRUMU': 'CD5402',
'MAHAGI': 'CD5407',
'MAMBASA': 'CD5403',
'BUNIA': 'CD5401'}
CompD3 = {'BUNIA': 'CD5401ZS01',
'ADI': 'CD5409ZS01',
'ADJA': 'CD5409ZS02',
'ARIWARA': 'CD5409ZS03',
'ARU': 'CD5409ZS04',
'BIRINGI': 'CD5409ZS05',
'LAYBO': 'CD5409ZS06',
'BAMBU': 'CD5405ZS01',
'DAMAS': 'CD5405ZS02',
'DRODRO': 'CD5405ZS03',
'FATAKI': 'CD5405ZS04',
'JIBA': 'CD5405ZS05',
'KILO': 'CD5405ZS06',
'LINGA': 'CD5405ZS07',
'LITA': 'CD5405ZS08',
'MANGALA': 'CD5405ZS09',
'MONGBALU': 'CD5405ZS10',
'NIZI': 'CD5405ZS11',
'RETHY': 'CD5405ZS12',
'TCHOMIA': 'CD5405ZS13',
'BOGA': 'CD5402ZS01',
'GETHY': 'CD5402ZS03',
'KOMANDA': 'CD5402ZS04',
'NYAKUNDE': 'CD5402ZS05',
'RWAMPARA': 'CD5402ZS06',
'ANGUMU': 'CD5407ZS01',
'AUNGBA': 'CD5407ZS02',
'KAMBALA': 'CD5407ZS03',
'LOGO': 'CD5407ZS04',
'MAHAGI': 'CD5407ZS05',
'NYARAMBE': 'CD5407ZS06',
'RIMBA': 'CD5407ZS07',
'LOLWA': 'CD5403ZS01',
'MAMBASA': 'CD5403ZS02',
'MANDIMA': 'CD5403ZS03',
'NIA-NIA': 'CD5403ZS04'}
elif Province == 'South Kivu':
CompD1 = {'SUD-KIVU': 'CD62'}
CompD2 = {'BUKAVU': 'CD6201',
'FIZI': 'CD6210',
'IDJWI': 'CD6206',
'KABARE': 'CD6202',
'KALEHE': 'CD6205',
'MWENGA': 'CD6212',
'SHABUNDA': 'CD6203',
'UVIRA': 'CD6208',
'WALUNGU': 'CD6207'}
CompD3 = {'BAGIRA': 'CD6201ZS01',
'IBANDA': 'CD6201ZS02',
'KADUTU': 'CD6201ZS03',
'FIZI': 'CD6210ZS01',
'KIMBI LULENGE': 'CD6210ZS02',
'MINEMBWE': 'CD6210ZS03',
'NUNDU': 'CD6210ZS04',
'IDJWI': 'CD6206ZS01',
'KABARE': 'CD6202ZS01',
'KATANA': 'CD6202ZS03',
'MITI MURHESA': 'CD6202ZS04',
'NYANTENDE': 'CD6202ZS05',
'BUNYAKIRI': 'CD6205ZS01',
'KALEHE': 'CD6205ZS02',
'KALONGE': 'CD6205ZS03',
'MINOVA': 'CD6205ZS04',
'ITOMBWE': 'CD6212ZS01',
'KAMITUGA': 'CD6212ZS02',
'KITUTU': 'CD6212ZS03',
'MWANA': 'CD6212ZS04',
'MWENGA': 'CD6212ZS05',
'KALOLE': 'CD6203ZS01',
'LULINGU': 'CD6203ZS02',
'MULUNGU': 'CD6203ZS03',
'SHABUNDA': 'CD6203ZS04',
'HAUT-PLATEAU': 'CD6208ZS01',
'LEMERA': 'CD6208ZS02',
'RUZIZI': 'CD6208ZS03',
'UVIRA': 'CD6208ZS04',
'KANIOLA': 'CD6207ZS05',
'KAZIBA': 'CD6207ZS01',
'MUBUMBANO': 'CD6207ZS02',
'NYANGEZI': 'CD6207ZS03',
'WALUNGU': 'CD6207ZS04'}
elif Province == 'Tanganyika':
CompD1 = {'TANGANYIKA': 'CD74'}
CompD2 = {'KABALO': 'CD7407',
'KALEMIE': 'CD7402',
'KONGOLO': 'CD7409',
'MANONO': 'CD7406',
'MOBA': 'CD7404',
'NYUNZU': 'CD7410'}
CompD3 = {'KABALO': 'CD7407ZS01',
'KALEMIE': 'CD7402ZS01',
'NYEMBA': 'CD7402ZS02',
'KONGOLO': 'CD7409ZS01',
'MBULULA': 'CD7409ZS02',
'ANKORO': 'CD7406ZS01',
'KIAMBI': 'CD7406ZS02',
'MANONO': 'CD7406ZS03',
'KANSIMBA': 'CD7404ZS01',
'MOBA': 'CD7404ZS02',
'NYUNZU': 'CD7410ZS01'}
Correct=True
#Province matches Territory
for index, row in df_grp_idp.iterrows():
if str(row['m0303_meta_adm1_pcode'])[:4] != str(row['m0304_meta_adm2_pcode'])[:4]:
print(f"Row {index} does not match: {row['m0303_meta_adm1_pcode']} -> {row['m0304_meta_adm2_pcode']}")
Correct = False
#Province matches Zone de Sante
for index, row in df_grp_idp.iterrows():
if str(row['m0303_meta_adm1_pcode'])[:4] != str(row['m0305_meta_adm3_pcode'])[:4]:
print(f"Row {index} does not match: {row['m0303_meta_adm1_pcode']} -> {row['m0305_meta_adm3_pcode']}")
Correct = False
#Territory matches Zone de Sante
for index, row in df_grp_idp.iterrows():
if str(row['m0304_meta_adm2_pcode'])[:6] != str(row['m0305_meta_adm3_pcode'])[:6]:
print(f"Row {index} does not match: {row['m0304_meta_adm2_pcode']} -> {row['m0305_meta_adm3_pcode']}")
Correct = False
#See if pcodes in the data match the correct forms in the dictionary
#Province Check
for index, row in df_grp_idp.iterrows():
label1 = row['m0303_meta_adm1_label']
pcode1 = row['m0303_meta_adm1_pcode']
if CompD1.get(label1) != (pcode1):
print(f"This row {index} does not match Province dictionary: {label1} -> {pcode1}")
Correct = False
#Territory Check
for index, row in df_grp_idp.iterrows():
label2 = row['m0304_meta_adm2_label']
pcode2 = row['m0304_meta_adm2_pcode']
if CompD2.get(label2) != (pcode2):
print(f"This row {index} does not match Territory dictionary: {label2} -> {pcode2}")
Correct = False
#ZS Check
for index, row in df_grp_idp.iterrows():
label3 = row['m0305_meta_adm3_label']
pcode3 = row['m0305_meta_adm3_pcode']
if CompD3.get(label3) != (pcode3):
print(f"This row {index} does not match ZS dictionary: {label3} -> {pcode3}")
Correct = False
if Correct:
print("Everything is correct")
If the code flags an error, it will print the row where the mistake is and tell me what P-Code is not matching. I will then need to discuss with the operations team what data needs to be removed or replaced.
If everything is good, then I get the below.
Finally, I need to check that the males and females in all the sheets equal the correct number of individuals. Below you can see the code I use for camps managed by other UN agencies. This data can be very difficult to collect, due to the constant flow of people and the difficult context on the ground.
As a result, I use two different methods to find whether the total number of males and females is equal to the total number of individuals – both make use of the ‘try…except’ method with simple conditional logic. This is because sometimes only information on the number of males and females is available. Other times, we get a breakdown of age brackets and gender together, all of which need to be equal to the total number of individuals. The code below accounts for both eventualities.
#Method 1 for checking demographics are correct for the CCCM
total_Males_CCCM = df_cm[['0 - 5 H', '6 - 17 H', '18 - 59 H', '60 + H']].sum().sum()
total_Females_CCCM = df_cm[['0 - 5 F', '6 - 17 F', '18 - 59 F', '60 + F']].sum().sum()
if total_Males_CCCM + total_Females_CCCM == df_cm['m0310_loc_num_idp_ind'].sum():
print('Correct Age Brackets Method 1: Males plus Females in "CCCM tab" is equal to the number of individuals.')
age_Bracket_CCCM = True
else:
print('Incorrect Age Brackets Method 1: Males plus Females in "CCCM tab" is NOT equal to the number of individuals')
age_Bracket_CCCM = False
print('\n')
except KeyError:
print('CCCM Tab is not in the Method 1 "age bracket" format')
print('\n')
#Method 2 for checking demographics are correct for the CCCM
try:
if df_cm['m2968_loc_num_men_idp'].sum() + df_cm['m2969_loc_num_women_idp'].sum() == df_cm['m0310_loc_num_idp_ind'].sum():
print('Correct Column Code Method 2: Males plus Females in "CCCM" is equal to the number of individuals.')
else:
print('Incorrect Column Code Method 2: Males plus Females in "CCCM" is NOT equal to the number of individuals')
print('\n')
except KeyError:
print('CCCM Tab is not in the Method 2 "column code" format')
print('\n')
The output below tells me that the CCCM data sheet contains gender data in the form ‘Age Brackets’ but not in the simple male column and female column format (called the “column code” format). However, that data does correctly equal the number of individuals. The non-CCCM data sheet. however, does not meet either condition and requires further investigation (in this case, some data had been inputted incorrectly and the male and female columns actually represented men and women, but not children under 18 – adding all three together produced the correct number of total individuals).
End of Part 1
There you have it, some simple code for data verification and cleaning. In the next part, I’ll be showcasing a large part of the analysis script used for the Mobility Tracking reports.