Reference
classifier_prediction
ClassifierPredictor
__init__(self, column_name=None, tfidf_path=None, model_path=None, label_encoder_path=None, agro_eco=False, gaul=False, calibrate=True, **kwargs)
special
A class that generates predictions based on a trained XGBoost model. Can predict based on Ugandan Regions, GAUL regions, or FAO agro-ecological zones. Optionally predicts gaul regions using an ensemble calibrated classifier.
Examples:
>>> surnames = pd.DataFrame({'names':['Ahimbisibwe', 'Auma', 'Amin',
'Makubuya', 'Museveni', 'Oculi', 'Kadaga']})
>>> c = ClassifierPredictor(column_name='names')
>>> predict_xgb = c.predict(surnames,
get_label_names=True,
predict_prob = True,
df_out =True)
Parameters:
| Name | Type | Description | Default |
|---|---|---|---|
column_name |
str |
When passing a pandas dataframe, the name of the columns with surnames. Defaults to None. |
None |
tfidf_path |
Union[str, pathlib.Path] |
the path of the joblib dump of the tfidf transformer. Defaults to None. |
None |
model_path |
Union[str, pathlib.Path] |
the path of the joblib dump of the trained model. Defaults to None. |
None |
label_encoder_path |
Union[str, pathlib.Path] |
the path of the joblib dump of the label encoder. Defaults to None. |
None |
agro_eco |
bool |
Whether to predict agro-ecological zones. Defaults to False. |
False |
gaul |
bool |
whether to predict gaul regions. Defaults to False. |
False |
calibrate |
bool |
whether to predict gaul regions. Defaults to True. |
True |
Source code in predictor/classifier_prediction.py
def __init__(
self,
column_name : str = None,
tfidf_path : Union[str, Path] =None,
model_path : Union[str, Path]=None,
label_encoder_path : Union[str, Path] =None,
agro_eco : bool=False,
gaul : bool =False,
calibrate : bool =True,
**kwargs
):
"""A class that generates predictions based on a trained XGBoost model. Can predict based on Ugandan Regions, GAUL regions, or FAO agro-ecological zones. Optionally predicts gaul regions using an ensemble calibrated classifier.
Example:
>>> surnames = pd.DataFrame({'names':['Ahimbisibwe', 'Auma', 'Amin',
'Makubuya', 'Museveni', 'Oculi', 'Kadaga']})
>>> c = ClassifierPredictor(column_name='names')
>>> predict_xgb = c.predict(surnames,
get_label_names=True,
predict_prob = True,
df_out =True)
Args:
column_name (str, optional): When passing a pandas dataframe, the name of the columns with surnames. Defaults to None.
tfidf_path (Union[str, Path], optional): the path of the joblib dump of the tfidf transformer. Defaults to None.
model_path (Union[str, Path], optional): the path of the joblib dump of the trained model. Defaults to None.
label_encoder_path (Union[str, Path], optional): the path of the joblib dump of the label encoder. Defaults to None.
agro_eco (bool, optional): Whether to predict agro-ecological zones. Defaults to False.
gaul (bool, optional): whether to predict gaul regions. Defaults to False.
calibrate (bool, optional): whether to predict gaul regions. Defaults to True.
"""
if agro_eco ==True and gaul==True:
raise Exception("You can't have agro_eco and gaul both true")
if tfidf_path is None:
if agro_eco:
tfidf_path = Path(
"predictor",
"saved_models",
"tfidf_multilabel_False_nokampala_True_agro_zone_smote_False_opt.joblib",
)
elif gaul:
tfidf_path = Path(
"predictor",
"saved_models",
"tfidf_multilabel_False_nokampala_True_gaul_smote_False_gaul_opt.joblib",
)
else:
tfidf_path = Path(
"predictor",
"saved_models",
"tfidf_multilabel_False_nokampala_True.joblib",
)
else:
tfidf_path = Path(tfidf_path)
if label_encoder_path is None:
if agro_eco:
label_encoder_path = Path(
"predictor",
"saved_models",
"label_encoder_multilabel_False_nokampala_True_agro_zone_smote_False_opt.joblib",
)
elif gaul:
label_encoder_path = Path(
"predictor",
"saved_models",
"label_encoder_multilabel_False_nokampala_True_gaul_smote_False_gaul_opt.joblib",
)
else:
label_encoder_path = Path(
"predictor",
"saved_models",
"label_encoder_multilabel_False_nokampala_True.joblib",
)
else:
label_encoder_path = Path(label_encoder_path)
if model_path is None:
if agro_eco:
model_path = Path(
"predictor",
"saved_models",
"xgb_None_multilabel_False_add_kampala_True_agro_zone_smote_False_opt.joblib",
)
elif gaul:
if calibrate:
model_path = Path(
"predictor",
'saved_models',
'xgb_None_calibrated_gaul_opt.joblib'
)
else:
model_path = Path(
"predictor",
"saved_models",
"xgb_None_multilabel_False_add_kampala_True_gaul_smote_False_gaul_opt.joblib"
)
else:
model_path = Path(
"predictor",
"saved_models",
"xgb_None_multilabel_False_add_kampala_True.joblib",
)
else:
model_path = Path(model_path)
self.tfidf_path = tfidf_path
self.model_path = model_path
self.label_encoder_path = label_encoder_path
self.column_name = column_name
load_label_encoder(self)
Loads label encoder. See here for more information.
Returns:
| Type | Description |
|---|---|
CountVectorizer |
Source code in predictor/classifier_prediction.py
def load_label_encoder(self) -> CountVectorizer:
"""Loads label encoder. See [here](https://scikit-learn.org/stable/modules/generated/sklearn.preprocessing.LabelEncoder.html)
for more information.
Returns:
CountVectorizer: An sklearn Count Vectorizer object
"""
return joblib.load(self.label_encoder_path)
load_model(self)
Loads pickled trained classifier. Current one is an XGBoost classifier. See here for more details.
Returns:
| Type | Description |
|---|---|
Union[xgboost.sklearn.XGBClassifier, sklearn.calibration.CalibratedClassifierCV] |
Union[XGBClassifier, CalibratedClassifierCV]: Depending on the option, either a trained XGBoost Classifier or an sklearn calibrated classifier. |
Source code in predictor/classifier_prediction.py
def load_model(self) -> Union[XGBClassifier, CalibratedClassifierCV]:
"""Loads pickled trained classifier. Current one is an XGBoost classifier.
See [here](https://xgboost.readthedocs.io/en/latest/) for more details.
Returns:
Union[XGBClassifier, CalibratedClassifierCV]: Depending on the option, either a trained XGBoost Classifier or an sklearn calibrated classifier.
"""
return joblib.load(self.model_path)
load_tfidf(self)
Loads Tfidf transformer. See here for information on all functionality.
Returns:
| Type | Description |
|---|---|
TfidfVectorizer |
Source code in predictor/classifier_prediction.py
def load_tfidf(self) -> TfidfVectorizer:
"""Loads Tfidf transformer. See [here](https://scikit-learn.org/stable/modules/generated/sklearn.feature_extraction.text.TfidfVectorizer.html) for information on all functionality.
Returns:
TfidfVectorizer: A sklearn Tfidf Vectorizer Object
"""
return joblib.load(self.tfidf_path)
predict(self, text=None, get_label_names=False, predict_prob=False, df_out=False)
Predicts origin based on classifier
Keyword Arguments: text (list): List of strings to be predicted (default: {None}) get_label_names (bool): Whether to output the label names after prediction (default: {False}) predict_prob (bool): whether to give probabilities of coming from each region (default: {False}) df_out (bool): whether to output a pandas dataframe (default: {False})
>>> from predictor.classifier_prediction import ClassifierPredictor
>>> # Instantiate predictor
>>> c = ClassifierPredictor()
>>> # Predict
>>> prediction = c.predict(['Auma'])
>>> print(prediction)
Returns:
| Type | Description |
|---|---|
Union[pandas.core.frame.DataFrame, list] |
Union[pd.DataFrame, list]: An object that contains predictions from the model |
Source code in predictor/classifier_prediction.py
def predict(self,
text : list =None,
get_label_names : bool=False,
predict_prob : bool=False,
df_out : bool=False) -> Union[pd.DataFrame, list]:
"""Predicts origin based on classifier
Keyword Arguments:
text (list): List of strings to be predicted (default: {None})
get_label_names (bool): Whether to output the label names after prediction (default: {False})
predict_prob (bool): whether to give probabilities of coming from each region (default: {False})
df_out (bool): whether to output a pandas dataframe (default: {False})
>>> from predictor.classifier_prediction import ClassifierPredictor
>>> # Instantiate predictor
>>> c = ClassifierPredictor()
>>> # Predict
>>> prediction = c.predict(['Auma'])
>>> print(prediction)
Returns:
Union[pd.DataFrame, list]: An object that contains predictions from the model
"""
labels = self.load_label_encoder()
raw_text_aux = text.copy(deep = True)
text = self.process_text(text)
raw_text = raw_text_aux.assign(processed_text = text).set_index('processed_text')
X = self.transform_text(text)
model = self.load_model()
if get_label_names:
if predict_prob:
label_names = labels.classes_
probs = model.predict_proba(X)
result = {
t: {
label_name: prob
for label_name, prob in zip(label_names, probs[i])
}
for t, i in zip(text, range(len(probs)))
}
else:
result = {
name: prediction
for name, prediction in zip(
text, labels.inverse_transform(model.predict(X))
)
}
else:
if predict_prob:
result = model.predict_proba(X)
else:
result = model.predict(X)
if df_out:
try:
return (
pd.DataFrame(result).T
.sort_index()
.merge(raw_text,
left_index = True,
right_index = True)
.reset_index()
.set_index(self.column_name)
.rename({'index' : 'processed_surname'}, axis=1)
)
except ValueError:
return pd.DataFrame(result.values(), index = result.keys()).rename({0 : 'prediction'}, axis=1)
else:
return result
process_text(self, text)
Pre-processes input text to make it ready for prediction.
Parameters:
| Name | Type | Description | Default |
|---|---|---|---|
text |
Union[str, list] |
The input string or list of strings that are to be processed |
required |
Returns:
| Type | Description |
|---|---|
list |
Source code in predictor/classifier_prediction.py
def process_text(self, text: Union[str, list]) -> list:
"""Pre-processes input text to make it ready for prediction.
Arguments:
text (str or list-like object): The input string or list of strings that are to be processed
Returns:
list: Pre-processed list of strings
"""
if isinstance(text, str):
text = [text]
elif isinstance(text, pd.DataFrame):
if self.column_name is None:
raise Exception("Got a dataframe, but did not get `column_name`")
text = text[self.column_name].tolist()
processed_text = [i.lower().rstrip().lstrip() for i in text]
return processed_text
transform_text(self, text=None)
A function that takes a list of surnames or strings and transforms them through the tf-idf transformer
Parameters:
| Name | Type | Description | Default |
|---|---|---|---|
text |
str |
the text to be transformed. Defaults to None. |
None |
Exceptions:
| Type | Description |
|---|---|
NoTextException |
Raises if no text was given |
Returns:
| Type | Description |
|---|---|
~Vector |
Source code in predictor/classifier_prediction.py
def transform_text(self, text : str = None) -> Vector:
"""A function that takes a list of surnames or strings and transforms them through the tf-idf transformer
Args:
text (str, optional): the text to be transformed. Defaults to None.
Raises:
NoTextException: Raises if no text was given
Returns:
Vector: A sparse matrix of numpy matrix
"""
if text is None:
raise NoTextException("No text was given for transformation.")
tfidf = self.load_tfidf()
return tfidf.transform(text)
exceptions
NoTextException
Raises error if text input is not given
table_predictor
TablePredictor
__init__(self, agro_eco=False, gaul=False, table_path=None, column_name=None)
special
A class for predicting origin based on the frequency of name occurrences. This is implemented using a simple frequency tables based on the voter registration data.
Parameters:
| Name | Type | Description | Default |
|---|---|---|---|
agro_eco |
bool |
Whether to predict agro-ecological zones. Defaults to False. |
False |
gaul |
bool |
whether to predict GAUL regions. Defaults to False. |
False |
table_path |
Union[str, pathlib.Path] |
Path to the CSV file with the frequency table. Defaults to None. |
None |
column_name |
str |
name of the column in the dataframe with the surname information. Defaults to None. |
None |
Source code in predictor/table_predictor.py
def __init__(self,
agro_eco : bool = False,
gaul : bool=False,
table_path : Union[str, Path] = None,
column_name : str = None):
"""A class for predicting origin based on the frequency of name occurrences. This is implemented using a simple frequency tables based on the voter registration data.
Args:
agro_eco (bool, optional): Whether to predict agro-ecological zones. Defaults to False.
gaul (bool, optional): whether to predict GAUL regions. Defaults to False.
table_path (Union[str, Path], optional): Path to the CSV file with the frequency table. Defaults to None.
column_name (str, optional): name of the column in the dataframe with the surname information. Defaults to None.
"""
if table_path is None:
if agro_eco:
table_path = Path('predictor',"table", "agro_zone_predictor.csv")
elif agro_eco==False and gaul==True:
table_path = Path('predictor', 'table', 'gaul_predictor.csv')
elif agro_eco == True and gaul == True:
raise Exception("Can't have agro_eco and gaul at the same time.")
else:
table_path = Path('predictor',"table", "table_predictor.csv")
self.table_path = table_path
self.table = self._load_table()
self.column_name = column_name
predict(self, text, fuzzy=False, n_jobs=1)
Predicts region probability by first trying to find an exact match and then fuzzy matching for any names not matched.
Parameters:
| Name | Type | Description | Default |
|---|---|---|---|
text |
list |
List of strings to predict |
required |
Keyword Arguments: n_jobs (int): The number of cores to use for fuzzy-matching. This only applies to fuzzy-matching. If all names are found by exact match, then multi-processing is not used at all. (default: {1})
Returns:
| Type | Description |
|---|---|
DataFrame |
pd.DataFrame: DataFrame of predictions |
Source code in predictor/table_predictor.py
def predict(self, text: list, fuzzy : bool = False, n_jobs : int = 1) -> pd.DataFrame:
"""Predicts region probability by first trying to find an exact match and then
fuzzy matching for any names not matched.
Arguments:
text (list): List of strings to predict
Keyword Arguments:
n_jobs (int): The number of cores to use for fuzzy-matching. This
only applies to fuzzy-matching. If all names are found by exact match, then
multi-processing is not used at all. (default: {1})
Returns:
pd.DataFrame: DataFrame of predictions
"""
processed_text = self._process_text(text = text)
# First do exact match
predicted_df = self._exact_match(text = processed_text, data = self.table)
if fuzzy:
# get dict of exact matches in same form as processed text
predicted_dict = dict(map(reversed, predicted_df['processed_surname'].to_dict().items()))
if predicted_df.index.size != len(processed_text):
difference = len(processed_text) - predicted_df.index.unique().size
print(f"Doing fuzzy matching for {difference} names...")
# Get names that weren't found
lost_dict = {k : processed_text[k] for k in set(processed_text) - set(predicted_dict)}
if n_jobs == 1:
fuzzy_matches = self._fuzzy_match(text = lost_dict, data = self.table)
elif n_jobs > 1:
_fuzzy_lost_dict = partial(self._fuzzy_match, text = lost_dict)
df_list = self._partition_table(data = self.table, n = n_jobs)
pool = Pool(processes = n_jobs)
results = pool.map(_fuzzy_lost_dict, df_list)
# Now get max probability for each name
results_df = pd.concat(results)
max_df = (
results_df
.groupby(results_df.index)
.max()
.merge(self.table, left_on = 'name_match', right_index = True)
)
predicted_df = predicted_df.append(max_df)
else:
raise Exception(f"{n_jobs} n_jobs not allowed as an input")
return predicted_df