During my six month stretch of time at Lambda Schools, I was privileged to learn an immense amount of knowledge as well as such a large variety of incredibly useful and powerful tools. It was in the last month of the program that I was given the opportunity to work for a great cause and help people who are trying to seek asylum. I was tasked with helping to develop an intuitive website that allows lawyers that are working with people who are seeking asylum to organize, upload, and get much more accurate and responsive information about each case that they are working with. Human Rights First is a nonprofit, international human rights organization based in New York City and Washington, D.C. To further that, the right of asylum is an ancient juridical concept, under which a person persecuted by one's own country may be protected by another sovereign authority, such as another country or sanctuary, who in medieval times could offer sanctuary. In this blog post I will explain my role on the team at Human Rights First and express not only my gratitude for being given the opportunity but also go over the features that were implemented in my time working on this project that hopefully will be able to assist many people who are either seeking asylum or to help the lawyers that are battling the asylum cases.
The first issue that I was tasked with had to do with how the data that we were working with was being received and sent to our endpoint to be retrieved by our web team. Below is a function that was included in the project when first I onboarded this mission.
def get_judge_side_bar(judge_name: str) -> go.Figure:
"""
Function grabs data from database by judge and creates a
side-by-side bar chart for visualization.
Input: judge_name (name of judge to filter by)
Output: fig (object with visualization data)
"""
df = get_judge_df(judge_name)
df["count"] = 1
grouped_df = df.groupby(by=["protected_grounds",
"outcome"]).agg({
"count": ["sum"]
}).reset_index()
fig = px.bar(grouped_df, x="protected_grounds", y="count",
color="outcome",
color_discrete_map={"granted": '#00D100',
"denied": "#D10000"},
barmode="group", title="Side-by-Side Bar Chart")
return fig
Although it is a well written and semi functioning, it causes issues when we migrated to a new database and had to be completely refactored. This was a few day process to get it to function to a new working state, below I will include my new and improved function that's main difference from the first is that it uses Panda's crosstab function opposed to grouping by two variables and performing a pandas aggregate.
def get_judge_vis(judge_id: int) -> Figure:
df = get_df()
judge_df = df[df["judge_id"] == judge_id]
cross_tab = pd.crosstab(
judge_df["protected_grounds"],
judge_df["outcome"],
)
data = [
go.Bar(name=col, x=cross_tab.index, y=cross_tab[col])
for col in cross_tab.columns
]
layout = go.Layout(
title="Outcome by Protected Grounds",
barmode="group",
)
return go.Figure(data, layout)
This was the first version of visualizations that was able to actually be displayed on the website since the birth of this project almost a year ago. It is very similar in the way in functions to the last edition but another key change worth noting is how the endpoint functions now. It is believed that the endpoint was the main cause of the issue but is not for certain. Below I will provide a sample screenshot of the final result that we were able to get to display as well as the endpoint that would function properly.
@app.get("/vis/judge/{judge_id}")
async def outcome_by_judge(judge_id: int):
"""
Endpoint for visualizations on outcome by protected grounds by judge using plotly
"""
return json.loads(get_judge_vis(judge_id).to_json())
The most crucial change in the endpoint is the JSON encoder that was completely required to properly send our requests to the web side. More notably the loads function at the start of our return. json.loads() method can be used to parse a valid JSON string and convert it into a python dictionary. It is mainly used for deserializing native string, byte, or byte array which consists of JSON data into python dictionary. Excellent this is exactly what we were needing. After I was able to get this function and endpoint working to display dynamic results based on the comparison of the outcome of the case and protected grounds of the case per judge, I wanted to next implement more functionality to improve the robustness of these visualizations.
def get_judge_feature_vis(judge_id: int, feature: str, feature_2: str) -> Figure:
# Changes the variable names to be more readible without underscores
feature_name = feature.title().replace('_', ' ')
feature_name_2 = feature.title().replace('_', ' ')
df = get_df()
judge_df = df[df["judge_id"] == judge_id]
cross_tab = pd.crosstab(
judge_df[feature],
judge_df[feature_2],
)
data = [
go.Bar(name=col, x=cross_tab.index, y=cross_tab[col])
for col in cross_tab.columns
]
layout = go.Layout(
title=f"{feature_name_2} by {feature_name}",
barmode="group",
)
return go.Figure(data, layout)
This was the second version of the visualizations that I was going to implement for further functionality. The key difference to note between this and the first functioning version is that I parameterized new variables that will be the features that we want to compare. My hope with this implementation was to allow for not just the comparison of outcome by protected ground but and value that may be desired.
@app.get("/vis/judge/{judge_id}/{feature}")
async def outcome_by_judge_and_feature(judge_id: int, feature: str):
"""
Endpoint for visualizations on outcome by protected grounds by judge using plotly
"""
return json.loads(get_judge_feature_vis(judge_id, feature).to_json())
This was the new endpoint that was required for the next step of the implementation and it's case is the same for the function the only change was the parameterization that allows for a feature of choice to be selected opposed to just outcome by protected ground.
Overall my time working for Human Rights First was an absolutely amazing experience, I learned so much about not only the technological side of how to work with large databases and APIs. Beyond that it was an incredibly enlightening to be able to work in such a diverse and operational team. I wish I had the option to continue working to further push this project because it's for such an import cause and because I had absolutely loved how great of a team we had developed to be. I encourage you to give Human Rights First's page a look, they work tirelessly daily only to help people who are in need of assistance and their team does a phenomenal job in doing so. If you feel inclined or are able to I will leave a link to donation for their cause below. Thank you for taking the time to read this, this specific project was the most important and impactful piece that I've been honored to work on so far in my data science career and I feel truly privileged to be able to use my tools and programming skills to make an actual difference by helping people.
For more information on their work and asylum project please check out this link.
Donate to Human Rights First here.