I have the following problem that I solved using Python (Numpy/Pandas) and the code is provided later on. I mainly program in Java and wrote this program for a job interview as Python developer. I eventually got rejected and got the feedback saying the effort is not satisfactory. So, I mainly look forward to the suggestions from the expert Python developers. Please, tell me what should I do to improve the program.
Terminal handling charges are charges for loading/offloading shipping containers in ports. The charges are characterised by the following attributes:
* port – expressed as a portcode, a 5-letter string uniquely identifying a port. Portcodes consist of 2-letter country code and 3-letter city code.
* supplier_id - integer, uniquely identifying the provider of the information
* currency - 3-letter string identifying the currency
* value - a floating-point number
However, all the terminal handling charges collected are to be considered as potentially erroneous as they are provided by untrustworthy sources and can hence contain various types of mistakes. It is therefore important to develop a method to validate and ensure the quality of the terminal handling charges.
Goal of the Exercise
Develop a method for inspecting the distribution of the terminal handling charges. Identify and mark outliers (potentially erroneous values) for exclusion from the utilized collection of charges.
Details
- We are providing a set of initial sample data in the exersise.
- It is sufficient to look at the data on a country level, since the amount of data for individual ports might not be sufficient.
Currencies
Charges can be in different currencies. All charges can be normalised to USD. Currency exchange information can be obtained from various sources, for example openexchangerates.com. It is safe to assume that the currency exchange can be calculated with the current day's rates.
System input and output
- The list of values per country should be visualised in a meaningful way, identifying the amount of good/bad charges present in the system.
- The system should be able to accept new entries, checking them with the developed algorithm, and storing them along with the existing data, marked either as ok or an outlier.
sample_data.json
[{
"currency": "CNY",
"supplier_id": 35,
"port": "CNAQG",
"value": 820.0
}, {
"currency": "CNY",
"supplier_id": 19,
"port": "CNAQG",
"value": 835.0
}, {
"currency": "CNY",
"supplier_id": 49,
"port": "CNAQG",
"value": 600.0
}, {
"currency": "CNY",
"supplier_id": 54,
"port": "CNAQG",
"value": 775.0
}, {
"currency": "CNY",
"supplier_id": 113,
"port": "CNAQG",
"value": 785.0
}]
constants.py
PATH_LOCATION = '../TerminalCharges/data'
SUPPORTED_CURRENCIES = {
"EUR": "European euro",
"USD": "US dollar",
"GBP": "Pound sterling",
"BRL": "Brazilian real",
"CNY": "Chinese Yuann"
}
COUNTRIES = {
'CN': 'CHINA',
'US': 'USA'
}
CURRENCIES_DIC = {'CN':'CHINA', 'US':'USA'}
LOW_Q = 0.05
HIGH_Q = 0.95
API_LINK = "https://api.fixer.io/latest?base={}&symbols={}"
terminal.py
import json
from pprint import pprint
import pandas as pd
import numpy as np
import requests
import matplotlib.pyplot as plt
from os import listdir, walk
from os.path import isfile, join
from constants import *
# get the exchange rate from the base
# to the target currencies
def get_exchange_rate(base_currency, target_currency):
if not (base_currency in SUPPORTED_CURRENCIES.keys()):
raise ValueError("base currency {} not supported".format(base_currency))
if not (target_currency in SUPPORTED_CURRENCIES.keys()):
raise ValueError("target currency {} not supported".format(target_currency))
if base_currency == target_currency:
return 1
api_uri = API_LINK.format(base_currency, target_currency)
api_response = requests.get(api_uri)
if api_response.status_code == 200:
return api_response.json()["rates"][target_currency]
# get the data after startdard cleansing
def get_cleaned_data(df):
df = df[df.notnull().all(axis=1)]
df.port = df.port.astype(str)
df.supplier_id = df.supplier_id.astype(int)
df.currency = df.currency.astype(str)
df.value = df.value.astype(float)
m1 = (df.port.astype(str).str.len() == 5) & (df.port.apply(lambda x :isinstance(x, str)))
m2 = df.supplier_id.apply(lambda x : isinstance(x, int))
m3=(df.currency.astype(str).str.len() == 3)&(df.currency.apply(lambda x :isinstance(x, str)))
m4 = df.value.apply(lambda x : isinstance(x, float))
mask = m1 & m2 & m3 & m4
df = df[mask]
row_count = df.shape[0]
col_count = df.shape[1]
return df
# convert to other currencies
# to the usd
def normalize_to_usd(df):
exchage_rate_to_usd = {}
for curr in SUPPORTED_CURRENCIES.keys():
exchage_rate_to_usd[curr] = get_exchange_rate(curr, 'USD')
for curr in SUPPORTED_CURRENCIES.keys():
df.loc[df.currency == curr, ['value']] = df['value']*exchage_rate_to_usd[curr]
df.loc[df.currency == curr, ['currency']] = 'USD'
df.value = df.value.round(2)
return df
# mark 10% of the data as outliers
def mark_outliers(df):
df["country"] = df["port"].str[:2].map(CURRENCIES_DIC)
df["outlier"] = df.groupby("country")["value"].transform(mark_outliers_helper)
row_count = df.shape[0]
col_count = df.shape[1]
return df
def mark_outliers_helper(series):
lower = series < series.quantile(LOW_Q)
upper = series > series.quantile(HIGH_Q)
return lower | upper
# get the percentage of the outlier
# for the respective country
def get_outliers_percentage(df):
gb = df[['country','outlier']].groupby('country').mean()
for row in gb.itertuples():
print('Percentage of outliers for {: <12}: {:.1f}%'.format(row[0], 100*row[1]))
# test the accumulative df for the dimensions
def test_df_dimensions(df, row_count):
n_matrix = np.array(df)
assert isinstance(n_matrix, np.ndarray) or \
isinstance(n_matrix, np.matrixlib.defmatrix.matrix)
# print n_matrix.shape, row_count, df.shape[1]
assert n_matrix.shape == (row_count, df.shape[1])
def preprocess(dfs, functions):
apply_rec = lambda f, d: f[0](d) if len(f) == 1 \
else apply_rec (f[1:], f[0](d))
storage = [apply_rec (functions, df) for df in dfs]
df = pd.concat(storage, ignore_index=True)
return df
def generic_file_reader(PATH_LOCATION):
dfs = []
row = 0
file_names = next(walk(PATH_LOCATION))[2]
for file_name in file_names:
if file_name.endswith('.json'):
df = pd.read_json(PATH_LOCATION + '/' + file_name)
row += df.shape[0]
# row += df.count()
elif file_name.endswith('.csv'):
df = pd.read_csv(PATH_LOCATION + '/' + file_name)
row += df.shape[0]
else:
print 'file type is not supported now'
dfs.append(df)
return dfs, row
def visualize(functions, df):
for fun in functions:
fun(df)
# visualize the data in the line
# graph with pivot table
def visualize_line_graph(df):
df = df[df.outlier != 1]
df = pd.pivot_table(df,
index='supplier_id',
columns = df['country'],
values='value',
aggfunc=np.mean,
fill_value=0)
df.CHINA = df.CHINA.round(2)
df.USA = df.USA.round(2)
df = df[(df['CHINA'] != 0) & (df['USA'] != 0)]
df.plot()
plt.show(block=True)
def visualize_box_plot(df):
df = df[df.outlier != 1]
df = pd.pivot_table(df,
index=df.index,
columns = df['country'],
values='value')
#print (df.mask(df == 0))
##alternative solution
#print (df.replace(0,np.nan))
# df.mask(df == 0).plot.box()
df.boxplot()
plt.show()
def main():
clean_up_funcs = [get_cleaned_data, normalize_to_usd, \
mark_outliers]
vis_funcs = [visualize_line_graph, visualize_box_plot]
dfs, row_count = generic_file_reader(PATH_LOCATION)
df = preprocess(dfs, clean_up_funcs)
test_df_dimensions(df, row_count)
get_outliers_percentage(df)
visualize(vis_funcs, df)
if __name__ == "__main__":
main()