From a500bdb65cbbfb21ba0718f26996198070da8a19 Mon Sep 17 00:00:00 2001 From: Marcel Milcent Date: Tue, 15 Dec 2020 20:22:29 -0300 Subject: [PATCH] docs --- docs/build/doctrees/api.doctree | Bin 0 -> 432063 bytes docs/build/doctrees/benford.doctree | Bin 0 -> 360769 bytes docs/build/doctrees/environment.pickle | Bin 0 -> 145509 bytes docs/build/doctrees/index.doctree | Bin 0 -> 6568 bytes docs/build/doctrees/modules.doctree | Bin 0 -> 2615 bytes docs/build/html/.buildinfo | 4 + docs/build/html/_modules/benford/benford.html | 2046 ++++++++++++++++ .../build/html/_modules/benford/expected.html | 322 +++ docs/build/html/_modules/benford/stats.html | 349 +++ docs/build/html/_modules/benford/utils.html | 338 +++ docs/build/html/_modules/benford/viz.html | 503 ++++ docs/build/html/_modules/index.html | 191 ++ docs/build/html/_sources/api.rst.txt | 38 + docs/build/html/_sources/index.rst.txt | 25 + docs/build/html/_sources/modules.rst.txt | 7 + docs/build/html/api.html | 2063 +++++++++++++++++ docs/build/html/genindex.html | 530 +++++ docs/build/html/index.html | 232 ++ docs/build/html/modules.html | 217 ++ docs/build/html/objects.inv | Bin 0 -> 827 bytes docs/build/html/py-modindex.html | 226 ++ docs/build/html/search.html | 207 ++ docs/build/html/searchindex.js | 1 + 23 files changed, 7299 insertions(+) create mode 100644 docs/build/doctrees/api.doctree create mode 100644 docs/build/doctrees/benford.doctree create mode 100644 docs/build/doctrees/environment.pickle create mode 100644 docs/build/doctrees/index.doctree create mode 100644 docs/build/doctrees/modules.doctree create mode 100644 docs/build/html/.buildinfo create mode 100644 docs/build/html/_modules/benford/benford.html create mode 100644 docs/build/html/_modules/benford/expected.html create mode 100644 docs/build/html/_modules/benford/stats.html create mode 100644 docs/build/html/_modules/benford/utils.html create mode 100644 docs/build/html/_modules/benford/viz.html create mode 100644 docs/build/html/_modules/index.html create mode 100644 docs/build/html/_sources/api.rst.txt create mode 100644 docs/build/html/_sources/index.rst.txt create mode 100644 docs/build/html/_sources/modules.rst.txt create mode 100644 docs/build/html/api.html create mode 100644 docs/build/html/genindex.html create mode 100644 docs/build/html/index.html create mode 100644 docs/build/html/modules.html create mode 100644 docs/build/html/objects.inv create mode 100644 docs/build/html/py-modindex.html create mode 100644 docs/build/html/search.html create mode 100644 docs/build/html/searchindex.js diff --git a/docs/build/doctrees/api.doctree b/docs/build/doctrees/api.doctree new file mode 100644 index 0000000000000000000000000000000000000000..3527800cb9100dd6431c27716cb3e41ff9342190 GIT binary patch literal 432063 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 G*53f{i)Bgx literal 0 HcmV?d00001 diff --git a/docs/build/doctrees/benford.doctree b/docs/build/doctrees/benford.doctree new file mode 100644 index 0000000000000000000000000000000000000000..599e43b19aba554ef0049597d809b996b19ad923 GIT binary patch literal 360769 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 z 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 U`IwZoToDZrTvtaHZH*59e~O8YOaK4? literal 0 HcmV?d00001 diff --git a/docs/build/doctrees/environment.pickle b/docs/build/doctrees/environment.pickle new file mode 100644 index 0000000000000000000000000000000000000000..1912b4d0cb23d80416129ea7b296d84c836fddc5 GIT binary patch literal 145509 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

-yQ-Ku~y+s@1+4x1eOxW78$rRFPkK+y7$ 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 literal 0 HcmV?d00001 diff --git a/docs/build/html/.buildinfo b/docs/build/html/.buildinfo new file mode 100644 index 0000000..465c10e --- /dev/null +++ b/docs/build/html/.buildinfo @@ -0,0 +1,4 @@ +# Sphinx build info version 1 +# This file hashes the configuration used when building these files. When it is not found, a full rebuild will be done. +config: 9ddb3104eab9aa94f5516d4542bf4c48 +tags: 645f666f9bcd5a90fca523b33c5a78b7 diff --git a/docs/build/html/_modules/benford/benford.html b/docs/build/html/_modules/benford/benford.html new file mode 100644 index 0000000..903ace3 --- /dev/null +++ b/docs/build/html/_modules/benford/benford.html @@ -0,0 +1,2046 @@ + + + + + + + + + + + benford.benford — benford_py 0.3.0 documentation + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + \ No newline at end of file diff --git a/docs/build/html/_modules/benford/expected.html b/docs/build/html/_modules/benford/expected.html new file mode 100644 index 0000000..bc238fd --- /dev/null +++ b/docs/build/html/_modules/benford/expected.html @@ -0,0 +1,322 @@ + + + + + + + + + + + benford.expected — benford_py 0.3.0 documentation + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + +
+ + + +
+ + + + + +
+ +
+ + + + + + + + + + + + + + + + + +
+ + + + +
+
+
+
+ +

Source code for benford.expected

+from pandas import DataFrame
+from numpy import array, arange, log10
+from .checks import _check_digs_
+from .viz import plot_expected
+
+
+
[docs]class First(DataFrame): + """Holds the expected probabilities of the First, First Two, or + First Three digits according to Benford's distribution. + + Args: + digs: 1, 2 or 3 - tells which of the first digits to consider: + 1 for the First Digit, 2 for the First Two Digits and 3 for + the First Three Digits. + plot: option to plot a bar chart of the Expected proportions. + Defaults to True. + save_plot: string with the path/name of the file in which the generated + plot will be saved. Uses matplotlib.pyplot.savefig(). File format + is infered by the file name extension. Only available when + plot=True. + save_plot_kwargs: dict with any of the kwargs accepted by + matplotlib.pyplot.savefig() + https://matplotlib.org/api/_as_gen/matplotlib.pyplot.savefig.html + Only available when plot=True and save_plot is a string with the + figure file path/name. + """ + + def __init__(self, digs, plot=True, save_plot=None, save_plot_kwargs=None): + _check_digs_(digs) + dig_name = f'First_{digs}_Dig' + Dig = arange(10 ** (digs - 1), 10 ** digs) + Exp = log10(1 + (1. / Dig)) + + DataFrame.__init__(self, {'Expected': Exp}, index=Dig) + self.index.names = [dig_name] + + if plot: + plot_expected(self, digs, save_plot=save_plot, + save_plot_kwargs=save_plot_kwargs)
+ + +
[docs]class Second(DataFrame): + """Holds the expected probabilities of the Second Digits + according to Benford's distribution. + + Args: + plot: option to plot a bar chart of the Expected proportions. + Defaults to True. + save_plot: string with the path/name of the file in which the generated + plot will be saved. Uses matplotlib.pyplot.savefig(). File format + is infered by the file name extension. Only available when + plot=True. + save_plot_kwargs: dict with any of the kwargs accepted by + matplotlib.pyplot.savefig() + https://matplotlib.org/api/_as_gen/matplotlib.pyplot.savefig.html + Only available when plot=True and save_plot is a string with the + figure file path/name. + """ + def __init__(self, plot=True, save_plot=None, save_plot_kwargs=None): + a = arange(10, 100) + Expe = log10(1 + (1. / a)) + Sec_Dig = array(list(range(10)) * 9) + + df = DataFrame({'Expected': Expe, 'Sec_Dig': Sec_Dig}) + + DataFrame.__init__(self, df.groupby('Sec_Dig').sum()) + + if plot: + plot_expected(self, 22, save_plot=save_plot, + save_plot_kwargs=save_plot_kwargs)
+ + +
[docs]class LastTwo(DataFrame): + """Holds the expected probabilities of the Last Two Digits + according to Benford's distribution. + + Args: + plot: option to plot a bar chart of the Expected proportions. + Defaults to True. + save_plot: string with the path/name of the file in which the generated + plot will be saved. Uses matplotlib.pyplot.savefig(). File format + is infered by the file name extension. Only available when + plot=True. + save_plot_kwargs: dict with any of the kwargs accepted by + matplotlib.pyplot.savefig() + https://matplotlib.org/api/_as_gen/matplotlib.pyplot.savefig.html + Only available when plot=True and save_plot is a string with the + figure file path/name. + """ + def __init__(self, num=False, plot=True, save_plot=None, save_plot_kwargs=None): + exp = array([1 / 99.] * 100) + DataFrame.__init__(self, {'Expected': exp, + 'Last_2_Dig': _lt_(num=num)}) + self.set_index('Last_2_Dig', inplace=True) + if plot: + plot_expected(self, -2, save_plot=save_plot, + save_plot_kwargs=save_plot_kwargs)
+ + +def _test_(digs): + """Chooses the Exxpected class to be used in a test + + Args: + digs: the int corresponding to the Expected class to be instantiated + + Returns: + the Expected instance forthe propoer test to be performed + """ + if digs in [1, 2, 3]: + return First(digs, plot=False) + elif digs == 22: + return Second(plot=False) + else: + return LastTwo(num=True, plot=False) + + +def _lt_(num=False): + """Creates an array with the possible last two digits + + Args: + num: returns numeric (ints) values. Defaluts to False, + which returns strings. + + Returns: + Array of ints or str, in any case representing all 100 possible + combinations of last two digits + """ + if num: + n = arange(0, 100) + else: + n = arange(0, 100).astype(str) + n[:10] = array(['00', '01', '02', '03', '04', '05', + '06', '07', '08', '09']) + return n +
+ +
+ +
+ + +
+
+ +
+ +
+ + + + + + + + + + + + \ No newline at end of file diff --git a/docs/build/html/_modules/benford/stats.html b/docs/build/html/_modules/benford/stats.html new file mode 100644 index 0000000..74d3fac --- /dev/null +++ b/docs/build/html/_modules/benford/stats.html @@ -0,0 +1,349 @@ + + + + + + + + + + + benford.stats — benford_py 0.3.0 documentation + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + +
+ + + +
+ + + + + +
+ +
+ + + + + + + + + + + + + + + + + +
+ + + + +
+
+
+
+ +

Source code for benford.stats

+from numpy import sqrt
+from .constants import crit_chi2, KS_crit, mad_dict, digs_dict
+
+
+
[docs]def Z_score(frame, N): + """Computes the Z statistics for the proportions studied + + Args: + frame: DataFrame with the expected proportions and the already calculated + Absolute Diferences between the found and expeccted proportions + N: sample size + + Returns: + Series of computed Z scores + """ + return (frame.AbsDif - (1 / (2 * N))) / sqrt( + (frame.Expected * (1. - frame.Expected)) / N)
+ + +
[docs]def chi_sq(frame, ddf, confidence, verbose=True): + """Comnputes the chi-square statistic of the found distributions and compares + it with the critical chi-square of such a sample, according to the + confidence level chosen and the degrees of freedom - len(sample) -1. + + Args: + frame: DataFrame with Found, Expected and their difference columns. + ddf: Degrees of freedom to consider. + confidence: Confidence level to look up critical value. + verbose: prints the chi-squre result and compares to the critical + chi-square for the sample. Defaults to True. + + Returns: + The computed Chi square statistic and the critical chi square + (according) to the degrees of freedom and confidence level, + for comparison. None if confidence is None + """ + if confidence is None: + print('\nChi-square test needs confidence other than None.') + return + else: + exp_counts = frame.Counts.sum() * frame.Expected + dif_counts = frame.Counts - exp_counts + found_chi = (dif_counts ** 2 / exp_counts).sum() + crit_chi = crit_chi2[ddf][confidence] + if verbose: + print(f"\nThe Chi-square statistic is {found_chi:.4f}.\n" + f"Critical Chi-square for this series: {crit_chi}.") + return (found_chi, crit_chi)
+ + +
[docs]def chi_sq_2(frame): + """Computes the chi-square statistic of the found distributions + + Args: + frame: DataFrame with Found, Expected and their difference columns. + + Returns: + The computed Chi square statistic + """ + exp_counts = frame.Counts.sum() * frame.Expected + dif_counts = frame.Counts - exp_counts + return (dif_counts ** 2 / exp_counts).sum()
+ + +
[docs]def kolmogorov_smirnov(frame, confidence, N, verbose=True): + """Computes the Kolmogorov-Smirnov test of the found distributions + and compares it with the critical chi-square of such a sample, + according to the confidence level chosen. + + Args: + frame: DataFrame with Foud and Expected distributions. + confidence: Confidence level to look up critical value. + N: Sample size + verbose: prints the KS result and the critical value for the sample. + Defaults to True. + + Returns: + The Suprem, which is the greatest absolute difference between the + Found and the expected proportions, and the Kolmogorov-Smirnov + critical value according to the confidence level, for ccomparison + """ + if confidence is None: + print('\nKolmogorov-Smirnov test needs confidence other than None.') + return + else: + # sorting and calculating the cumulative distribution + ks_frame = frame.sort_index()[['Found', 'Expected']].cumsum() + # finding the supremum - the largest cumul dist difference + suprem = ((ks_frame.Found - ks_frame.Expected).abs()).max() + # calculating the crittical value according to confidence + crit_KS = KS_crit[confidence] / sqrt(N) + + if verbose: + print(f"\nThe Kolmogorov-Smirnov statistic is {suprem:.4f}.\n" + f"Critical K-S for this series: {crit_KS:.4f}") + return (suprem, crit_KS)
+ + +
[docs]def kolmogorov_smirnov_2(frame): + """Computes the Kolmogorov-Smirnov test of the found distributions + + Args: + frame: DataFrame with Foud and Expected distributions. + + Returns: + The Suprem, which is the greatest absolute difference between the + Found end th expected proportions + """ + # sorting and calculating the cumulative distribution + ks_frame = frame.sort_index()[['Found', 'Expected']].cumsum() + # finding the supremum - the largest cumul dist difference + return ((ks_frame.Found - ks_frame.Expected).abs()).max()
+ + +
[docs]def mad(frame, test, verbose=True): + """Computes the Mean Absolute Deviation (MAD) between the found and the + expected proportions. + + Args: + frame: DataFrame with the Absolute Deviations already calculated. + test: Test to compute the MAD from (F1D, SD, F2D...) + verbose: prints the MAD result and compares to limit values of + conformity. Defaults to True. + + Returns: + The Mean of the Absolute Deviations between the found and expected + proportions. + """ + mad = frame.AbsDif.mean() + + if verbose: + print(f"\nThe Mean Absolute Deviation is {mad}") + + if test != -2: + print(f"For the {mad_dict[digs_dict[test]]}:\n\ + - 0.0000 to {mad_dict[test][0]}: Close Conformity\n\ + - {mad_dict[test][0]} to {mad_dict[test][1]}: Acceptable Conformity\n\ + - {mad_dict[test][1]} to {mad_dict[test][2]}: Marginally Acceptable Conformity\n\ + - Above {mad_dict[test][2]}: Nonconformity") + else: + pass + return mad
+ + +
[docs]def mse(frame, verbose=True): + """Computes the test's Mean Square Error + + Args: + frame: DataFrame with the already computed Absolute Deviations between + the found and expected proportions + verbose: Prints the MSE. Defaults to True. + + Returns: + Mean of the squared differences between the found and the expected proportions. + """ + mse = (frame.AbsDif ** 2).mean() + + if verbose: + print(f"\nMean Square Error = {mse}") + + return mse
+
+ +
+ +
+ + +
+
+ +
+ +
+ + + + + + + + + + + + \ No newline at end of file diff --git a/docs/build/html/_modules/benford/utils.html b/docs/build/html/_modules/benford/utils.html new file mode 100644 index 0000000..48f9f84 --- /dev/null +++ b/docs/build/html/_modules/benford/utils.html @@ -0,0 +1,338 @@ + + + + + + + + + + + benford.utils — benford_py 0.3.0 documentation + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + +
+ + + +
+ + + + + +
+ +
+ + + + + + + + + + + + + + + + + +
+ + + + +
+
+
+
+ +

Source code for benford.utils

+from pandas import Series, DataFrame
+from numpy import array, arange, log10, ndarray
+from .expected import _test_
+from .constants import digs_dict
+from .stats import Z_score
+
+
+def _set_N_(len_df, limit_N):
+    """"""
+    # Assigning to N the superior limit or the lenght of the series
+    if limit_N is None or limit_N > len_df:
+        return len_df
+    # Check on limit_N being a positive integer
+    else:
+        if limit_N < 0 or not isinstance(limit_N, int):
+            raise ValueError("limit_N must be None or a positive integer.")
+        else:
+            return limit_N
+
+
+
[docs]def get_mantissas(arr): + """Computes the mantissas, the non-integer part of the log of a number. + + Args: + arr: array of integers or floats + + Returns: + Array of floats withe logs mantissas + """ + log_a = abs(log10(arr)) + return log_a - log_a.astype(int) # the number - its integer part
+ + +
[docs]def input_data(given): + """Internalizes and transforms the input data + + Args: + given: ndarray, Series or tuple with DataFrame and name of the + column to analyze + + Returns: + The raw inputed data and the result of its first pre-processing, + when required. + """ + if type(given) == Series: + data = chosen = given + elif type(given) == ndarray: + data = given + chosen = Series(given) + elif type(given) == tuple: + if (type(given[0]) != DataFrame) | (type(given[1]) != str): + raise TypeError('The data tuple must be composed of a pandas ' + 'DataFrame and the name (str) of the chosen ' + 'column, in that order') + data = given[0] + chosen = given[0][given[1]] + else: + raise TypeError("Wrong data input type. Check docstring.") + return data, chosen
+ + +
[docs]def prepare(data, digs, limit_N, simple=False, confidence=None): + """Transforms the original number sequence into a DataFrame reduced + by the ocurrences of the chosen digits, creating other computed + columns + """ + N = _set_N_(len(data), limit_N=limit_N) + + # get the number of occurrences of the digits + v = data.value_counts() + # get their relative frequencies + p = data.value_counts(normalize=True) + # crate dataframe from them + dd = DataFrame({'Counts': v, 'Found': p}).sort_index() + # join the dataframe with the one of expected Benford's frequencies + dd = _test_(digs).join(dd).fillna(0) + # create column with absolute differences + dd['Dif'] = dd.Found - dd.Expected + dd['AbsDif'] = dd.Dif.abs() + if simple: + del dd['Dif'] + return dd + else: + if confidence is not None: + dd['Z_score'] = Z_score(dd, N) + return N, dd
+ +
[docs]def subtract_sorted(data): + """Subtracts the sorted sequence elements from each other, discarding zeros. + Used in the Second Order test + """ + sec = data.copy() + sec.sort_values(inplace=True) + sec = sec - sec.shift(1) + sec = sec.loc[sec != 0] + return sec
+ +
[docs]def prep_to_roll(start, test): + """Used by the rolling mad and rolling mean, prepares each test and + respective expected proportions for later application to the Series subset + """ + if test in [1, 2, 3]: + start[digs_dict[test]] = start.ZN // 10 ** (( + log10(start.ZN).astype(int)) - (test - 1)) + start = start.loc[start.ZN >= 10 ** (test - 1)] + + ind = arange(10 ** (test - 1), 10 ** test) + Exp = log10(1 + (1. / ind)) + + elif test == 22: + start[digs_dict[test]] = (start.ZN // 10 ** (( + log10(start.ZN)).astype(int) - 1)) % 10 + start = start.loc[start.ZN >= 10] + + Expec = log10(1 + (1. / arange(10, 100))) + temp = DataFrame({'Expected': Expec, 'Sec_Dig': + array(list(range(10)) * 9)}) + Exp = temp.groupby('Sec_Dig').sum().values.reshape(10,) + ind = arange(0, 10) + + else: + start[digs_dict[test]] = start.ZN % 100 + start = start.loc[start.ZN >= 1000] + + ind = arange(0, 100) + Exp = array([1 / 99.] * 100) + + return Exp, ind
+ +
[docs]def mad_to_roll(arr, Exp, ind): + """Mean Absolute Deviation used in the rolling function + """ + prop = Series(arr) + prop = prop.value_counts(normalize=True).sort_index() + + if len(prop) < len(Exp): + prop = prop.reindex(ind).fillna(0) + + return abs(prop - Exp).mean()
+ +
[docs]def mse_to_roll(arr, Exp, ind): + """Mean Squared Error used in the rolling function + """ + prop = Series(arr) + temp = prop.value_counts(normalize=True).sort_index() + + if len(temp) < len(Exp): + temp = temp.reindex(ind).fillna(0) + + return ((temp - Exp) ** 2).mean()
+
+ +
+ +
+ + +
+
+ +
+ +
+ + + + + + + + + + + + \ No newline at end of file diff --git a/docs/build/html/_modules/benford/viz.html b/docs/build/html/_modules/benford/viz.html new file mode 100644 index 0000000..593d71c --- /dev/null +++ b/docs/build/html/_modules/benford/viz.html @@ -0,0 +1,503 @@ + + + + + + + + + + + benford.viz — benford_py 0.3.0 documentation + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + +
+ + + +
+ + + + + +
+ +
+ + + + + + + + + + + + + + + + + +
+ + + + +
+
+
+
+ +

Source code for benford.viz

+from numpy import array, arange, maximum, sqrt, ones
+import matplotlib.pyplot as plt
+from matplotlib.text import Annotation
+from .constants import colors, mad_dict
+
+
+
[docs]def plot_expected(df, digs, save_plot=None, save_plot_kwargs=None): + """Plots the Expected Benford Distributions + + Args: + df: DataFrame with the Expected Proportions + digs: Test's digit + save_plot: string with the path/name of the file in which the generated + plot will be saved. Uses matplotlib.pyplot.savefig(). File format + is infered by the file name extension. + save_plot_kwargs: dict with any of the kwargs accepted by + matplotlib.pyplot.savefig() + https://matplotlib.org/api/_as_gen/matplotlib.pyplot.savefig.html + """ + if digs in [1, 2, 3]: + y_max = (df.Expected.max() + (10 ** -(digs) / 3)) * 100 + figsize = 2 * (digs ** 2 + 5), 1.5 * (digs ** 2 + 5) + elif digs == 22: + y_max = 13. + figsize = 14, 10.5 + elif digs == -2: + y_max = 1.1 + figsize = 15, 8 + fig, ax = plt.subplots(figsize=figsize) + plt.title('Expected Benford Distributions', size='xx-large') + plt.xlabel(df.index.name, size='x-large') + plt.ylabel('Distribution (%)', size='x-large') + ax.set_facecolor(colors['b']) + ax.set_ylim(0, y_max) + ax.bar(df.index, df.Expected * 100, color=colors['t'], align='center') + ax.set_xticks(df.index) + ax.set_xticklabels(df.index) + + if save_plot: + if not save_plot_kwargs: + save_plot_kwargs = {} + plt.savefig(save_plot, **save_plot_kwargs) + + plt.show(block=False)
+ + +def _get_plot_args(digs): + """Selects the correct arguments for the plotting functions, depending on the + the test (digs) chosen. + """ + if digs in [1, 2, 3]: + text_x = False + n, m = 10 ** (digs - 1), 10 ** (digs) + x = arange(n, m) + figsize = (2 * (digs ** 2 + 5), 1.5 * (digs ** 2 + 5)) + elif digs == 22: + text_x = False + x = arange(10) + figsize = (14, 10) + else: + text_x = True + x = arange(100) + figsize = (15, 7) + return x, figsize, text_x + +
[docs]def plot_digs(df, x, y_Exp, y_Found, N, figsize, conf_Z, text_x=False, + save_plot=None, save_plot_kwargs=None): + """Plots the digits tests results + + Args: + df: DataFrame with the data to be plotted + x: sequence to be used in the x axis + y_Exp: sequence of the expected proportions to be used in the y axis + (line) + y_Found: sequence of the found proportions to be used in the y axis + (bars) + N: lenght of sequence, to be used when plotting the confidence levels + figsize: tuple to state the size of the plot figure + conf_Z: Confidence level + save_pic: file path to save figure + text_x: Forces to show all x ticks labels. Defaluts to True. + save_plot: string with the path/name of the file in which the generated + plot will be saved. Uses matplotlib.pyplot.savefig(). File format + is infered by the file name extension. + save_plot_kwargs: dict with any of the kwargs accepted by + matplotlib.pyplot.savefig() + https://matplotlib.org/api/_as_gen/matplotlib.pyplot.savefig.html + + """ + if len(x) > 10: + rotation = 90 + else: + rotation = 0 + fig, ax = plt.subplots(figsize=figsize) + plt.title('Expected vs. Found Distributions', size='xx-large') + plt.xlabel('Digits', size='x-large') + plt.ylabel('Distribution (%)', size='x-large') + if conf_Z is not None: + sig = conf_Z * sqrt(y_Exp * (1 - y_Exp) / N) + upper = y_Exp + sig + (1 / (2 * N)) + lower_zeros = array([0]*len(upper)) + lower = maximum(y_Exp - sig - (1 / (2 * N)), lower_zeros) + u = (y_Found < lower) | (y_Found > upper) + c = array([colors['m']] * len(u)) + c[u] = colors['af'] + lower *= 100. + upper *= 100. + ax.plot(x, upper, color=colors['s'], zorder=5) + ax.plot(x, lower, color=colors['s'], zorder=5) + ax.fill_between(x, upper, lower, color=colors['s'], + alpha=.3, label='Conf') + else: + c = colors['m'] + ax.bar(x, y_Found * 100., color=c, label='Found', zorder=3, align='center') + ax.plot(x, y_Exp * 100., color=colors['s'], linewidth=2.5, + label='Benford', zorder=4) + ax.set_xticks(x) + ax.set_xticklabels(x, rotation=rotation) + ax.set_facecolor(colors['b']) + if text_x: + ind = array(df.index).astype(str) + ind[:10] = array(['00', '01', '02', '03', '04', '05', + '06', '07', '08', '09']) + plt.xticks(x, ind, rotation='vertical') + ax.legend() + ax.set_ylim(0, max([y_Exp.max() * 100, y_Found.max() * 100]) + 10 / len(x)) + ax.set_xlim(x[0] - 1, x[-1] + 1) + + if save_plot: + if not save_plot_kwargs: + save_plot_kwargs = {} + plt.savefig(save_plot, **save_plot_kwargs) + + plt.show(block=False)
+ + +
[docs]def plot_sum(df, figsize, li, text_x=False, save_plot=None, save_plot_kwargs=None): + """Plots the summation test results + + Args: + df: DataFrame with the data to be plotted + figsize: sets the dimensions of the plot figure + li: value with which to draw the horizontal line + save_plot: string with the path/name of the file in which the generated + plot will be saved. Uses matplotlib.pyplot.savefig(). File format + is infered by the file name extension. + save_plot_kwargs: dict with any of the kwargs accepted by + matplotlib.pyplot.savefig() + https://matplotlib.org/api/_as_gen/matplotlib.pyplot.savefig.html + """ + x = df.index + rotation = 90 if len(x) > 10 else 0 + fig = plt.figure(figsize=figsize) + ax = fig.add_subplot(111) + plt.title('Expected vs. Found Sums') + plt.xlabel('Digits') + plt.ylabel('Sums') + ax.bar(x, df.Percent, color=colors['m'], + label='Found Sums', zorder=3, align='center') + ax.set_xlim(x[0] - 1, x[-1] + 1) + ax.axhline(li, color=colors['s'], linewidth=2, label='Expected', zorder=4) + ax.set_xticks(x) + ax.set_xticklabels(x, rotation=rotation) + ax.set_facecolor(colors['b']) + if text_x: + ind = array(x).astype(str) + ind[:10] = array(['00', '01', '02', '03', '04', '05', + '06', '07', '08', '09']) + plt.xticks(x, ind, rotation='vertical') + ax.legend() + + if save_plot: + if not save_plot_kwargs: + save_plot_kwargs = {} + plt.savefig(save_plot, **save_plot_kwargs) + + plt.show(block=False)
+ +
[docs]def plot_ordered_mantissas(col, figsize=(12, 12), + save_plot=None, save_plot_kwargs=None): + """Plots the ordered mantissas and compares them to the expected, straight + line that should be formed in a Benford-cmpliant set. + + Args: + col (Series): column of mantissas to plot. + figsize (tuple): sets the dimensions of the plot figure. + save_plot: string with the path/name of the file in which the generated + plot will be saved. Uses matplotlib.pyplot.savefig(). File format + is infered by the file name extension. + save_plot_kwargs: dict with any of the kwargs accepted by + matplotlib.pyplot.savefig() + https://matplotlib.org/api/_as_gen/matplotlib.pyplot.savefig.html + + """ + ld = len(col) + x = arange(1, ld + 1) + n = ones(ld) / ld + fig = plt.figure(figsize=figsize) + ax = fig.add_subplot(111) + ax.plot(x, col.sort_values(), linestyle='--', + color=colors['s'], linewidth=3, label='Mantissas') + ax.plot(x, n.cumsum(), color=colors['m'], + linewidth=2, label='Expected') + plt.ylim((0, 1.)) + plt.xlim((1, ld + 1)) + ax.set_facecolor(colors['b']) + ax.set_title("Ordered Mantissas") + plt.legend(loc='upper left') + + if save_plot: + if not save_plot_kwargs: + save_plot_kwargs = {} + plt.savefig(save_plot, **save_plot_kwargs) + + plt.show(block=False);
+ +
[docs]def plot_mantissa_arc_test(df, gravity_center, grid=True, figsize=12, + save_plot=None, save_plot_kwargs=None): + """Draws thee Mantissa Arc Test after computing X and Y circular coordinates + for every mantissa and the center of gravity for the set + + Args: + df (DataFrame): pandas DataFrame with the mantissas and the X and Y + coordinates. + gravity_center (tuple): coordinates for plottling the gravity center + grid (bool): show grid. Defaults to True. + figsize (int): figure dimensions. No need to be a tuple, since the + figure is a square. + save_plot: string with the path/name of the file in which the generated + plot will be saved. Uses matplotlib.pyplot.savefig(). File format + is infered by the file name extension. + save_plot_kwargs: dict with any of the kwargs accepted by + matplotlib.pyplot.savefig() + https://matplotlib.org/api/_as_gen/matplotlib.pyplot.savefig.html + """ + fig = plt.figure(figsize=(figsize, figsize)) + ax = plt.subplot() + ax.set_facecolor(colors['b']) + ax.scatter(df.mant_x, df.mant_y, label="ARC TEST", + color=colors['m']) + ax.scatter(gravity_center[0], gravity_center[1], + color=colors['s']) + text_annotation = Annotation( + " Gravity Center: " + f"x({round(gravity_center[0], 3)})," + f" y({round(gravity_center[1], 3)})", + xy=(gravity_center[0] - 0.65, + gravity_center[1] - 0.1), + xycoords='data') + ax.add_artist(text_annotation) + ax.grid(True, which='both') + ax.axhline(y=0, color='k') + ax.axvline(x=0, color='k') + ax.legend(loc='lower left') + ax.set_title("Mantissas Arc Test") + + if save_plot: + if not save_plot_kwargs: + save_plot_kwargs = {} + plt.savefig(save_plot, **save_plot_kwargs) + + plt.show(block=False);
+ +
[docs]def plot_roll_mse(roll_series, figsize, save_plot=None, save_plot_kwargs=None): + """Shows the rolling MSE plot + + Args: + roll_series: pd.Series resultant form rolling mse. + figsize: the figure dimensions. + save_plot: string with the path/name of the file in which the generated + plot will be saved. Uses matplotlib.pyplot.savefig(). File format + is infered by the file name extension. + save_plot_kwargs: dict with any of the kwargs accepted by + matplotlib.pyplot.savefig() + https://matplotlib.org/api/_as_gen/matplotlib.pyplot.savefig.html + """ + fig, ax = plt.subplots(figsize=figsize) + ax.set_facecolor(colors['b']) + ax.plot(roll_series, color=colors['m']) + + if save_plot: + if not save_plot_kwargs: + save_plot_kwargs = {} + plt.savefig(save_plot, **save_plot_kwargs) + + plt.show(block=False)
+ +
[docs]def plot_roll_mad(roll_mad, figsize, save_plot=None, save_plot_kwargs=None): + """Shows the rolling MAD plot + + Args: + roll_mad: pd.Series resultant form rolling mad. + figsize: the figure dimensions. + save_plot: string with the path/name of the file in which the generated + plot will be saved. Uses matplotlib.pyplot.savefig(). File format + is infered by the file name extension. + save_plot_kwargs: dict with any of the kwargs accepted by + matplotlib.pyplot.savefig() + https://matplotlib.org/api/_as_gen/matplotlib.pyplot.savefig.html + """ + fig, ax = plt.subplots(figsize=figsize) + ax.set_facecolor(colors['b']) + ax.plot(roll_mad.roll_series, color=colors['m']) + + if roll_mad.test != -2: + plt.axhline(y=mad_dict[roll_mad.test][0], color=colors['af'], linewidth=3) + plt.axhline(y=mad_dict[roll_mad.test][1], color=colors['h2'], linewidth=3) + plt.axhline(y=mad_dict[roll_mad.test][2], color=colors['s'], linewidth=3) + + if save_plot: + if not save_plot_kwargs: + save_plot_kwargs = {} + plt.savefig(save_plot, **save_plot_kwargs) + + plt.show(block=False)
+
+ +
+ +
+ + +
+
+ +
+ +
+ + + + + + + + + + + + \ No newline at end of file diff --git a/docs/build/html/_modules/index.html b/docs/build/html/_modules/index.html new file mode 100644 index 0000000..9d8e321 --- /dev/null +++ b/docs/build/html/_modules/index.html @@ -0,0 +1,191 @@ + + + + + + + + + + + Overview: module code — benford_py 0.3.0 documentation + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + +
+ + + +
+ + + + + +
+ +
+ + + + + + + + + + + + + + + + + +
+ +
    + +
  • Docs »
  • + +
  • Overview: module code
  • + + +
  • + +
  • + +
+ + +
+
+
+
+ +

All modules for which code is available

+ + +
+ +
+ + +
+
+ +
+ +
+ + + + + + + + + + + + \ No newline at end of file diff --git a/docs/build/html/_sources/api.rst.txt b/docs/build/html/_sources/api.rst.txt new file mode 100644 index 0000000..c62789b --- /dev/null +++ b/docs/build/html/_sources/api.rst.txt @@ -0,0 +1,38 @@ +benford package +=============== + +benford.benford module +---------------------- + +.. automodule:: benford.benford + :members: + :undoc-members: + :show-inheritance: + + +benford.expected module +----------------------- + +.. automodule:: benford.expected + :members: + :undoc-members: + :show-inheritance: + + +benford.stats module +-------------------- + +.. automodule:: benford.stats + :members: + :undoc-members: + :show-inheritance: + + +benford.viz module +------------------ + +.. automodule:: benford.viz + :members: + :undoc-members: + :show-inheritance: + diff --git a/docs/build/html/_sources/index.rst.txt b/docs/build/html/_sources/index.rst.txt new file mode 100644 index 0000000..037870f --- /dev/null +++ b/docs/build/html/_sources/index.rst.txt @@ -0,0 +1,25 @@ +Welcome to benford_py's documentation! +====================================== + +.. toctree:: + :maxdepth: 3 + :caption: Contents: + + modules + + +Indices and tables +================== + +* :ref:`genindex` +* :ref:`modindex` +* :ref:`search` + +On GitHub +--------- + +`Package `_ +~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~ + +`Demo Jupyter Notebook `_ +~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~ diff --git a/docs/build/html/_sources/modules.rst.txt b/docs/build/html/_sources/modules.rst.txt new file mode 100644 index 0000000..dd1c132 --- /dev/null +++ b/docs/build/html/_sources/modules.rst.txt @@ -0,0 +1,7 @@ +benford +======= + +.. toctree:: + :maxdepth: 2 + + api diff --git a/docs/build/html/api.html b/docs/build/html/api.html new file mode 100644 index 0000000..38edaa8 --- /dev/null +++ b/docs/build/html/api.html @@ -0,0 +1,2063 @@ + + + + + + + + + + + benford package — benford_py 0.3.0 documentation + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + +
+ + + +
+ + + + + +
+ +
+ + + + + + + + + + + + + + + + + +
+ + + + +
+
+
+
+ +
+

benford package

+
+

benford.benford module

+
+
+class benford.benford.Base(data, decimals, sign='all', sec_order=False)[source]
+

Bases: pandas.core.frame.DataFrame

+

Internalizes and prepares the data for Analysis.

+
+
Parameters
+
    +
  • data – sequence of numbers to be evaluated. Must be a numpy 1D array, +a pandas Series or a pandas DataFrame column, with values being +integers or floats.

  • +
  • decimals – number of decimal places to consider. Defaluts to 2. +If integers, set to 0. If set to -infer-, it will remove the zeros +and consider up to the fifth decimal place to the right, but will +loose performance.

  • +
  • sign – tells which portion of the data to consider. pos: only the positive +entries; neg: only negative entries; all: all entries but zeros. +Defaults to all.`

  • +
+
+
Raises
+

TypeError – if not receiving int or float as input.

+
+
+
+ +
+
+class benford.benford.Test(base, digs, confidence, limit_N=None, sec_order=False)[source]
+

Bases: pandas.core.frame.DataFrame

+

Transforms the original number sequence into a DataFrame reduced +by the ocurrences of the chosen digits, creating other computed +columns

+
+
Parameters
+
    +
  • base – The Base object with the data prepared for Analysis

  • +
  • digs – Tells which test to perform: 1: first digit; 2: first two digits; +3: furst three digits; 22: second digit; -2: last two digits.

  • +
  • confidence – confidence level to draw lower and upper limits when +plotting and to limit the top deviations to show.

  • +
  • limit_N – sets a limit to N as the sample size for the calculation of +the Z scores if the sample is too big. Defaults to None.

  • +
+
+
+
+
+N
+

Number of records in the sample to consider in computations

+
+ +
+
+ddf
+

Degrees of Freedom to look up for the critical chi-square value

+
+ +
+
+chi_square
+

Chi-square statistic for the given test

+
+ +
+
+KS
+

Kolmogorov-Smirnov statistic for the given test

+
+ +
+
+MAD
+

Mean Absolute Deviation for the given test

+
+ +
+
+confidence
+

Confidence level to consider when setting some critical values

+
+ +
+
+digs
+

numerical representation of the test at hand. 1: F1D; 2: F2D; +3: F3D; 22: SD; -2: L2D.

+
+
Type
+

int

+
+
+
+ +
+
+sec_order
+

True if the test is a Second Order one

+
+
Type
+

bool

+
+
+
+ +
+
+update_confidence(new_conf, check=True)[source]
+

Sets a new confidence level for the Benford object, so as to be used to +produce critical values for the tests

+
+
Parameters
+
    +
  • new_conf – new confidence level to draw lower and upper limits when +plotting and to limit the top deviations to show, as well as to +calculate critical values for the tests’ statistics.

  • +
  • check – checks the value provided for the confidence. Defaults to True

  • +
+
+
+
+ +
+
+property critical_values
+

a dictionary with the critical values for the test at hand, +according to the current confidence level.

+
+
Type
+

dict

+
+
+
+ +
+
+show_plot(save_plot=None, save_plot_kwargs=None)[source]
+

Draws the test plot.

+
+
Parameters
+
    +
  • save_plot – string with the path/name of the file in which the generated +plot will be saved. Uses matplotlib.pyplot.savefig(). File format +is infered by the file name extension.

  • +
  • save_plot_kwargs – dict with any of the kwargs accepted by +matplotlib.pyplot.savefig() +https://matplotlib.org/api/_as_gen/matplotlib.pyplot.savefig.html +Only available when save_plot is a string with the figure file +path/name.

  • +
+
+
+
+ +
+
+report(high_Z='pos', show_plot=True, save_plot=None, save_plot_kwargs=None)[source]
+

Handles the report especific to the test, considering its statistics +and according to the current confidence level.

+
+
Parameters
+
    +
  • high_Z – chooses which Z scores to be used when displaying results, +according to the confidence level chosen. Defaluts to ‘pos’, +which will highlight only values higher than the expexted +frequencies; ‘all’ will highlight both extremes (positive and +negative); and an integer, which will use the first n entries, +positive and negative, regardless of whether Z is higher than +the critical value or not.

  • +
  • show_plot – calls the show_plot method, to draw the test plot

  • +
  • save_plot – string with the path/name of the file in which the generated +plot will be saved. Uses matplotlib.pyplot.savefig(). File format +is infered by the file name extension. Only available when +plot=True.

  • +
  • save_plot_kwargs – dict with any of the kwargs accepted by +matplotlib.pyplot.savefig() +https://matplotlib.org/api/_as_gen/matplotlib.pyplot.savefig.html +Only available when plot=True and save_plot is a string with the +figure file path/name.

  • +
+
+
+
+ +
+ +
+
+class benford.benford.Summ(base, test)[source]
+

Bases: pandas.core.frame.DataFrame

+

Gets the base object and outputs a Summation test object

+
+
Parameters
+
    +
  • base – The Base object with the data prepared for Analysis

  • +
  • test – The test for which to compute the summation

  • +
+
+
+
+
+MAD = None
+

Mean Absolute Deviation for the test

+
+ +
+
+confidence = None
+

Confidence level to consider when setting some critical values

+
+ +
+
+show_plot(save_plot=None, save_plot_kwargs=None)[source]
+

Draws the Summation test plot

+
+
Parameters
+
    +
  • save_plot – string with the path/name of the file in which the generated +plot will be saved. Uses matplotlib.pyplot.savefig(). File format +is infered by the file name extension.

  • +
  • save_plot_kwargs – dict with any of the kwargs accepted by +matplotlib.pyplot.savefig() +https://matplotlib.org/api/_as_gen/matplotlib.pyplot.savefig.html +Only available when save_plot is a string with the figure file +path/name.

  • +
+
+
+
+ +
+
+report(high_diff=None, show_plot=True, save_plot=None, save_plot_kwargs=None)[source]
+

Gives the report on the Summation test.

+
+
Parameters
+
    +
  • high_diff – Number of records to show after ordering by the absolute +differences between the found and the expected proportions

  • +
  • show_plot – calls the show_plot method, to draw the Summation test plot

  • +
  • save_plot – string with the path/name of the file in which the generated +plot will be saved. Uses matplotlib.pyplot.savefig(). File format +is infered by the file name extension. Only available when +plot=True.

  • +
  • save_plot_kwargs – dict with any of the kwargs accepted by +matplotlib.pyplot.savefig() +https://matplotlib.org/api/_as_gen/matplotlib.pyplot.savefig.html +Only available when plot=True and save_plot is a string with the +figure file path/name.

  • +
+
+
+
+ +
+ +
+
+class benford.benford.Benford(data, decimals=2, sign='all', confidence=95, mantissas=False, sec_order=False, summation=False, limit_N=None, verbose=True)[source]
+

Bases: object

+

Initializes a Benford Analysis object and computes the proportions for +the digits. The tets dataFrames are atributes, i.e., obj.F1D is the First +Digit DataFrame, the obj.F2D,the First Two Digits one, and so one, F3D for +First Three Digits, SD for Second Digit and L2D for Last Two Digits.

+
+
Parameters
+
    +
  • data – sequence of numbers to be evaluated. Must be a numpy 1D array, +a pandas Series or a tuple with a pandas DataFrame and the name (str) +of the chosen column. Values must be integers or floats.

  • +
  • decimals – number of decimal places to consider. Defaluts to 2. +If integers, set to 0. If set to -infer-, it will remove the zeros +and consider up to the fifth decimal place to the right, but will +loose performance.

  • +
  • sign – tells which portion of the data to consider. pos: only the positive +entries; neg: only negative entries; all: all entries but zeros. +Defaults to all.

  • +
  • confidence – confidence level to draw lower and upper limits when +plotting and to limit the top deviations to show, as well as to +calculate critical values for the tests’ statistics. Defaults to 95.

  • +
  • sec_order – runs the Second Order tests, which are the Benford’s tests +performed on the differences between the ordered sample (a value minus +the one before it, and so on). If the original series is Benford- +compliant, this new sequence should aldo follow Beford. The Second +Order can also be called separately, through the method sec_order().

  • +
  • summation – creates the Summation DataFrames for the First, First Two, and +First Three Digits. The summation tests can also be called separately, +through the method summation().

  • +
  • limit_N – sets a limit to N as the sample size for the calculation of +the Z scores if the sample is too big. Defaults to None.

  • +
  • verbose – gives some information about the data and the registries used +and discarded for each test.

  • +
+
+
+
+
+data
+

the raw data provided for the analysis

+
+ +
+
+chosen
+

the column of the DataFrame to be analysed or the data itself

+
+ +
+
+sign
+

which number sign(s) to include in the analysis

+
+
Type
+

str

+
+
+
+ +
+
+confidence
+

current confidence level

+
+ +
+
+limit_N
+

sample size to use in computations

+
+
Type
+

int

+
+
+
+ +
+
+verbose
+

verbose or not

+
+
Type
+

bool

+
+
+
+ +
+
+base
+

the Base, pre-processed object

+
+ +
+
+tests
+

keeps track of the tests the +instance has

+
+
Type
+

list of str

+
+
+
+ +
+
+update_confidence(new_conf, tests=None)[source]
+

Sets (a) new confidence level(s) for the Benford object, so as to be +used to produce critical values for the tests.

+
+
Parameters
+
    +
  • new_conf – new confidence level to draw lower and upper limits when +plotting and to limit the top deviations to show, as well as to +calculate critical values for the tests’ statistics.

  • +
  • tests (list of str) – list of tests names (strings) to +have their confidence updated. If only one, provide a one-element +list, like [‘F1D’]. Defauts to None, in which case it will use +the instance .test list attribute.

  • +
+
+
Raises
+

ValueError – if the test argument is not a list or None.

+
+
+
+ +
+
+property all_confidences
+

a dictionary with a confidence level for each computed tests, +when applicable.

+
+
Type
+

dict

+
+
+
+ +
+
+mantissas()[source]
+

Adds a Mantissas object to the tests, with all its statistics and +plotting capabilities.

+
+ +
+
+sec_order()[source]
+

Runs the Second Order tests, which are the Benford’s tests +performed on the differences between the ordered sample (a value minus +the one before it, and so on). If the original series is Benford- +compliant, this new sequence should aldo follow Beford. The Second +Order can also be called separately, through the method sec_order().

+
+ +
+
+summation()[source]
+

Creates Summation test DataFrames from Base object

+
+ +
+ +
+
+class benford.benford.Source(data, decimals=2, sign='all', sec_order=False, verbose=True, inform=None)[source]
+

Bases: pandas.core.frame.DataFrame

+

Prepares the data for Analysis. pandas DataFrame subclass.

+
+
Parameters
+
    +
  • data – sequence of numbers to be evaluated. Must be a numpy 1D array, +a pandas Series or a pandas DataFrame column, with values being +integers or floats.

  • +
  • decimals – number of decimal places to consider. Defaluts to 2. +If integers, set to 0. If set to -infer-, it will remove the zeros +and consider up to the fifth decimal place to the right, but will +loose performance.

  • +
  • sign – tells which portion of the data to consider. pos: only the positive +entries; neg: only negative entries; all: all entries but zeros. +Defaults to all.

  • +
  • sec_order – choice for the Second Order Test, which cumputes the +differences between the ordered entries before running the Tests.

  • +
  • verbose – tells the number of registries that are being subjected to +the analysis; defaults to True.

  • +
+
+
Raises
+
    +
  • ValueError – if the sign arg is not in [‘all’, ‘pos’, ‘neg’]

  • +
  • TypeError – if not receiving int or float as input.

  • +
+
+
+
+
+verbose = None
+

verbose or not

+
+
Type
+

(bool)

+
+
+
+ +
+
+mantissas(report=True, show_plot=True, figsize=(15, 8), save_plot=None, save_plot_kwargs=None)[source]
+

Calculates the mantissas, their mean and variance, and compares them +with the mean and variance of a Benford’s sequence.

+
+
Parameters
+
    +
  • report – prints the mamtissas mean, variance, skewness and kurtosis +for the sequence studied, along with reference values.

  • +
  • show_plot – plots the ordered mantissas and a line with the expected +inclination. Defaults to True.

  • +
  • figsize – tuple that sets the figure dimensions.

  • +
  • save_plot – string with the path/name of the file in which the generated +plot will be saved. Uses matplotlib.pyplot.savefig(). File format +is infered by the file name extension. Only available when +plot=True.

  • +
  • save_plot_kwargs – dict with any of the kwargs accepted by +matplotlib.pyplot.savefig() +https://matplotlib.org/api/_as_gen/matplotlib.pyplot.savefig.html +Only available when plot=True and save_plot is a string with the +figure file path/name.

  • +
+
+
+
+ +
+
+first_digits(digs, confidence=None, high_Z='pos', limit_N=None, MAD=False, MSE=False, chi_square=False, KS=False, show_plot=True, save_plot=None, save_plot_kwargs=None, simple=False, ret_df=False)[source]
+

Performs the Benford First Digits test with the series of +numbers provided, and populates the mapping dict for future +selection of the original series.

+
+
Parameters
+
    +
  • digs – number of first digits to consider. Must be 1 (first digit), +2 (first two digits) or 3 (first three digits).

  • +
  • verbose – tells the number of registries that are being subjected to +the analysis; defaults to True

  • +
  • digs – number of first digits to consider. Must be 1 (first digit), +2 (first two digits) or 3 (first three digits).

  • +
  • confidence – confidence level to draw lower and upper limits when +plotting and to limit the top deviations to show, as well as to +calculate critical values for the tests’ statistics. Defaults to None.

  • +
  • high_Z – chooses which Z scores to be used when displaying results, +according to the confidence level chosen. Defaluts to ‘pos’, +which will highlight only values higher than the expexted +frequencies; ‘all’ will highlight both extremes (positive and +negative); and an integer, which will use the first n entries, +positive and negative, regardless of whether Z is higher than +the confidence or not.

  • +
  • limit_N – sets a limit to N as the sample size for the calculation of +the Z scores if the sample is too big. Defaults to None.

  • +
  • MAD – calculates the Mean Absolute Difference between the +found and the expected distributions; defaults to False.

  • +
  • MSE – calculates the Mean Square Error of the sample; defaults to +False.

  • +
  • show_plot – draws the test plot. Defaults to True.

  • +
  • save_plot – string with the path/name of the file in which the generated +plot will be saved. Uses matplotlib.pyplot.savefig(). File format +is infered by the file name extension. Only available when +plot=True.

  • +
  • save_plot_kwargs – dict with any of the kwargs accepted by +matplotlib.pyplot.savefig() +https://matplotlib.org/api/_as_gen/matplotlib.pyplot.savefig.html +Only available when plot=True and save_plot is a string with the +figure file path/name.

  • +
  • ret_df – returns the test DataFrame. Defaults to False. True if run by +the test function.

  • +
+
+
Returns
+

+
DataFrame with the Expected and Found proportions, and the Z scores of

the differences

+
+
+

+
+
+
+ +
+
+second_digit(confidence=None, high_Z='pos', limit_N=None, MAD=False, MSE=False, chi_square=False, KS=False, show_plot=True, save_plot=None, save_plot_kwargs=None, simple=False, ret_df=False)[source]
+

Performs the Benford Second Digit test with the series of +numbers provided.

+
+
Parameters
+
    +
  • verbose – tells the number of registries that are being subjected to +the analysis; defaults to True

  • +
  • MAD – calculates the Mean Absolute Difference between the +found and the expected distributions; defaults to False.

  • +
  • confidence – confidence level to draw lower and upper limits when +plotting and to limit the top deviations to show, as well as to +calculate critical values for the tests’ statistics. Defaults to None.

  • +
  • high_Z – chooses which Z scores to be used when displaying results, +according to the confidence level chosen. Defaluts to ‘pos’, +which will highlight only values higher than the expexted +frequencies; ‘all’ will highlight both extremes (positive and +negative); and an integer, which will use the first n entries, +positive and negative, regardless of whether Z is higher than +the confidence or not.

  • +
  • limit_N – sets a limit to N as the sample size for the calculation of +the Z scores if the sample is too big. Defaults to None.

  • +
  • MSE – calculates the Mean Square Error of the sample; defaults to +False.

  • +
  • show_plot – draws the test plot.

  • +
  • save_plot – string with the path/name of the file in which the generated +plot will be saved. Uses matplotlib.pyplot.savefig(). File format +is infered by the file name extension. Only available when +plot=True.

  • +
  • save_plot_kwargs – dict with any of the kwargs accepted by +matplotlib.pyplot.savefig() +https://matplotlib.org/api/_as_gen/matplotlib.pyplot.savefig.html +Only available when plot=True and save_plot is a string with the +figure file path/name.

  • +
  • ret_df – returns the test DataFrame. Defaults to False. True if run by +the test function.

  • +
+
+
Returns
+

+
DataFrame with the Expected and Found proportions, and the Z scores of

the differences

+
+
+

+
+
+
+ +
+
+last_two_digits(confidence=None, high_Z='pos', limit_N=None, MAD=False, MSE=False, chi_square=False, KS=False, show_plot=True, save_plot=None, save_plot_kwargs=None, simple=False, ret_df=False)[source]
+

Performs the Benford Last Two Digits test with the series of +numbers provided.

+
+
Parameters
+
    +
  • verbose – tells the number of registries that are being subjected to +the analysis; defaults to True

  • +
  • MAD – calculates the Mean Absolute Difference between the +found and the expected distributions; defaults to False.

  • +
  • confidence – confidence level to draw lower and upper limits when +plotting and to limit the top deviations to show, as well as to +calculate critical values for the tests’ statistics. Defaults to None.

  • +
  • high_Z – chooses which Z scores to be used when displaying results, +according to the confidence level chosen. Defaluts to ‘pos’, +which will highlight only values higher than the expexted +frequencies; ‘all’ will highlight both extremes (positive and +negative); and an integer, which will use the first n entries, +positive and negative, regardless of whether Z is higher than +the confidence or not.

  • +
  • limit_N – sets a limit to N as the sample size for the calculation of +the Z scores if the sample is too big. Defaults to None.

  • +
  • MSE – calculates the Mean Square Error of the sample; defaults to +False.

  • +
  • show_plot – draws the test plot.

  • +
  • save_plot – string with the path/name of the file in which the generated +plot will be saved. Uses matplotlib.pyplot.savefig(). File format +is infered by the file name extension. Only available when +plot=True.

  • +
  • save_plot_kwargs – dict with any of the kwargs accepted by +matplotlib.pyplot.savefig() +https://matplotlib.org/api/_as_gen/matplotlib.pyplot.savefig.html +Only available when plot=True and save_plot is a string with the +figure file path/name.

  • +
+
+
Returns
+

+
DataFrame with the Expected and Found proportions, and the Z scores of

the differences

+
+
+

+
+
+
+ +
+
+summation(digs=2, top=20, show_plot=True, save_plot=None, save_plot_kwargs=None, ret_df=False)[source]
+

Performs the Summation test. In a Benford series, the sums of the +entries begining with the same digits tends to be the same.

+
+
Parameters
+
    +
  • digs – tells the first digits to use. 1- first; 2- first two; +3- first three. Defaults to 2.

  • +
  • top – choses how many top values to show. Defaults to 20.

  • +
  • show_plot – plots the results. Defaults to True.

  • +
  • save_plot – string with the path/name of the file in which the generated +plot will be saved. Uses matplotlib.pyplot.savefig(). File format +is infered by the file name extension. Only available when +plot=True.

  • +
  • save_plot_kwargs – dict with any of the kwargs accepted by +matplotlib.pyplot.savefig() +https://matplotlib.org/api/_as_gen/matplotlib.pyplot.savefig.html +Only available when plot=True and save_plot is a string with the +figure file path/name.

  • +
+
+
Returns
+

+
DataFrame with the Expected and Found proportions, and their

absolute differences

+
+
+

+
+
+
+ +
+
+duplicates(top_Rep=20, inform=None)[source]
+

Performs a duplicates test and maps the duplicates count in descending +order.

+
+
Parameters
+
    +
  • verbose – tells how many duplicated entries were found and prints the +top numbers according to the top_Rep argument. Defaluts to True.

  • +
  • top_Rep – int or None. Chooses how many duplicated entries will be +shown withe the top repititions. Defaluts to 20. If None, returns +al the ordered repetitions.

  • +
+
+
Returns
+

+
DataFrame with the duplicated records and their occurrence counts,

in descending order (if verbose is False; if True, prints to +terminal).

+
+
+

+
+
Raises
+

ValueError – if the top_Rep arg is not int or None.

+
+
+
+ +
+ +
+
+class benford.benford.Mantissas(data)[source]
+

Bases: object

+

Returns a Series with the data mantissas,

+
+
Parameters
+

data – sequence to compute mantissas from, numpy 1D array, pandas +Series of pandas DataFrame column.

+
+
+
+
+data
+

holds the computed mantissas and, if the arc_test +is also called, the respecttive x and Y coordinates for the plot.

+
+
Type
+

DataFrame

+
+
+
+ +
+
+stats
+

holds the relevant statistics about the data mantissas.

+
+
Type
+

dict

+
+
+
+ +
+
+data = None
+

pandas DataFrame with the mantissas

+
+
Type
+

(DataFrame)

+
+
+
+ +
+
+report(show_plot=True, save_plot=None, save_plot_kwargs=None)[source]
+

Displays the Mantissas stats.

+
+
Parameters
+
    +
  • show_plot – shows the ordered mantissas plot and the Arc Test plot. +Defaults to True.

  • +
  • save_plot – string with the path/name of the file in which the generated +plot will be saved. Uses matplotlib.pyplot.savefig(). File format +is infered by the file name extension. Only available when +plot=True.

  • +
  • save_plot_kwargs – dict with any of the kwargs accepted by +matplotlib.pyplot.savefig() +https://matplotlib.org/api/_as_gen/matplotlib.pyplot.savefig.html +Only available when plot=True and save_plot is a string with the +figure file path/name.

  • +
+
+
+
+ +
+
+show_plot(figsize=(12, 12), save_plot=None, save_plot_kwargs=None)[source]
+

Plots the ordered mantissas and compares them to the expected, straight +line that should be formed in a Benford-cmpliant set.

+
+
Parameters
+
    +
  • figsize – tuple that sets the figure size.

  • +
  • save_plot – string with the path/name of the file in which the generated +plot will be saved. Uses matplotlib.pyplot.savefig(). File format +is infered by the file name extension.

  • +
  • save_plot_kwargs – dict with any of the kwargs accepted by +matplotlib.pyplot.savefig() +https://matplotlib.org/api/_as_gen/matplotlib.pyplot.savefig.html +Only available when save_plot is a string with the figure file +path/name.

  • +
+
+
+
+ +
+
+arc_test(grid=True, figsize=12, save_plot=None, save_plot_kwargs=None)[source]
+

Add two columns to Mantissas’s DataFrame equal to their “X” and “Y” +coordinates, plots its to a scatter plot and calculates the gravity +center of the circle.

+
+
Parameters
+
    +
  • grid – show grid of the plot. Defaluts to True.

  • +
  • figsize – size of the figure to be displayed. Since it is a square, +there is no need to provide a tuple, like is usually the case with +matplotlib.

  • +
  • save_plot – string with the path/name of the file in which the generated +plot will be saved. Uses matplotlib.pyplot.savefig(). File format +is infered by the file name extension.

  • +
  • save_plot_kwargs – dict with any of the kwargs accepted by +matplotlib.pyplot.savefig() +https://matplotlib.org/api/_as_gen/matplotlib.pyplot.savefig.html +Only available when save_plot is a string with the figure file +path/name.

  • +
+
+
+
+ +
+ +
+
+class benford.benford.Roll_mad(data, test, window, decimals=2, sign='all')[source]
+

Bases: object

+

Applies the MAD to sequential subsets of the Series, returning another +Series.

+
+
Parameters
+
    +
  • data – sequence of numbers to be evaluated. Must be a numpy 1D array, +a pandas Series or a pandas DataFrame column, with values being +integers or floats.

  • +
  • test – tells which test to use. 1: Fisrt Digits; 2: First Two Digits; +3: First Three Digits; 22: Second Digit; and -2: Last Two Digits.

  • +
  • window – size of the subset to be used.

  • +
  • decimals – number of decimal places to consider. Defaluts to 2. +If integers, set to 0. If set to -infer-, it will remove the zeros +and consider up to the fifth decimal place to the right, but will +loose performance.

  • +
  • sign – tells which portion of the data to consider. pos: only the positive +entries; neg: only negative entries; all: all entries but zeros. +Defaults to all.

  • +
+
+
+
+
+test = None
+

the test (F1D, SD, F2D…) used for the MAD calculation and critical values

+
+ +
+
+show_plot(figsize=(15, 8), save_plot=None, save_plot_kwargs=None)[source]
+

Shows the rolling MAD plot

+
+
Parameters
+
    +
  • figsize – the figure dimensions.

  • +
  • save_plot – string with the path/name of the file in which the generated +plot will be saved. Uses matplotlib.pyplot.savefig(). File format +is infered by the file name extension.

  • +
  • save_plot_kwargs – dict with any of the kwargs accepted by +matplotlib.pyplot.savefig() +https://matplotlib.org/api/_as_gen/matplotlib.pyplot.savefig.html +Only available when save_plot is a string with the figure file +path/name.

  • +
+
+
+
+ +
+ +
+
+class benford.benford.Roll_mse(data, test, window, decimals=2, sign='all')[source]
+

Bases: object

+

Applies the MSE to sequential subsets of the Series, returning another +Series.

+
+
Parameters
+
    +
  • data – sequence of numbers to be evaluated. Must be a numpy 1D array, +a pandas Series or a pandas DataFrame column, with values being +integers or floats.

  • +
  • test – tells which test to use. 1: Fisrt Digits; 2: First Two Digits; +3: First Three Digits; 22: Second Digit; and -2: Last Two Digits.

  • +
  • window – size of the subset to be used. +decimals: number of decimal places to consider. Defaluts to 2. +If integers, set to 0. If set to -infer-, it will remove the zeros +and consider up to the fifth decimal place to the right, but will +loose performance.

  • +
  • sign – tells which portion of the data to consider. ‘pos’: only the positive +entries; ‘neg’: only negative entries; ‘all’: all entries but zeros. +Defaults to ‘all’.

  • +
+
+
+
+
+show_plot(figsize=(15, 8), save_plot=None, save_plot_kwargs=None)[source]
+

Shows the rolling MSE plot

+
+
Parameters
+
    +
  • figsize – the figure dimensions.

  • +
  • save_plot – string with the path/name of the file in which the generated +plot will be saved. Uses matplotlib.pyplot.savefig(). File format +is infered by the file name extension.

  • +
  • save_plot_kwargs – dict with any of the kwargs accepted by +matplotlib.pyplot.savefig() +https://matplotlib.org/api/_as_gen/matplotlib.pyplot.savefig.html +Only available when save_plot is a string with the figure file +path/name.

  • +
+
+
+
+ +
+ +
+
+benford.benford.first_digits(data, digs, decimals=2, sign='all', verbose=True, confidence=None, high_Z='pos', limit_N=None, MAD=False, MSE=False, chi_square=False, KS=False, show_plot=True, save_plot=None, save_plot_kwargs=None, inform=None)[source]
+

Performs the Benford First Digits test on the series of +numbers provided.

+
+
Parameters
+
    +
  • data – sequence of numbers to be evaluated. Must be a numpy 1D array, +a pandas Series or a pandas DataFrame column, with values being +integers or floats.

  • +
  • decimals – number of decimal places to consider. Defaluts to 2. +If integers, set to 0. If set to -infer-, it will remove the zeros +and consider up to the fifth decimal place to the right, but will +loose performance.

  • +
  • sign – tells which portion of the data to consider. ‘pos’: only the positive +entries; ‘neg’: only negative entries; ‘all’: all entries but zeros. +Defaults to ‘all’.

  • +
  • digs – number of first digits to consider. Must be 1 (first digit), +2 (first two digits) or 3 (first three digits).

  • +
  • verbose – tells the number of registries that are being subjected to +the analysis and returns tha analysis DataFrame sorted by the +highest Z score down. Defaults to True.

  • +
  • MAD – calculates the Mean Absolute Difference between the +found and the expected distributions; defaults to False.

  • +
  • confidence – confidence level to draw lower and upper limits when +plotting and to limit the top deviations to show. Defaults to None.

  • +
  • high_Z – chooses which Z scores to be used when displaying results, +according to the confidence level chosen. Defaluts to ‘pos’, +which will highlight only values higher than the expexted +frequencies; ‘all’ will highlight both extremes (positive and +negative); and an integer, which will use the first n entries, +positive and negative, regardless of whether Z is higher than +the confidence or not.

  • +
  • limit_N – sets a limit to N as the sample size for the calculation of +the Z scores if the sample is too big. Defaults to None.

  • +
  • MSE – calculates the Mean Square Error of the sample; defaults to +False.

  • +
  • chi_square – calculates the chi_square statistic of the sample and +compares it with a critical value, according to the confidence +level chosen and the series’s degrees of freedom. Defaults to +False. Requires confidence != None.

  • +
  • KS – calculates the Kolmogorov-Smirnov test, comparing the cumulative +distribution of the sample with the Benford’s, according to the +confidence level chosen. Defaults to False. Requires confidence +!= None.

  • +
  • show_plot – draws the test plot.

  • +
  • save_plot – string with the path/name of the file in which the generated +plot will be saved. Uses matplotlib.pyplot.savefig(). File format +is infered by the file name extension. Only available when +plot=True.

  • +
  • save_plot_kwargs – dict with any of the kwargs accepted by +matplotlib.pyplot.savefig() +https://matplotlib.org/api/_as_gen/matplotlib.pyplot.savefig.html +Only available when plot=True and save_plot is a string with the +figure file path/name.

  • +
+
+
Returns
+

+
DataFrame with the Expected and Found proportions, and the Z scores of

the differences if the confidence is not None.

+
+
+

+
+
+
+ +
+
+benford.benford.second_digit(data, decimals=2, sign='all', verbose=True, confidence=None, high_Z='pos', limit_N=None, MAD=False, MSE=False, chi_square=False, KS=False, show_plot=True, save_plot=None, save_plot_kwargs=None, inform=None)[source]
+

Performs the Benford Second Digits test on the series of +numbers provided.

+
+
Parameters
+
    +
  • data – sequence of numbers to be evaluated. Must be a numpy 1D array, +a pandas Series or a pandas DataFrame column, with values being +integers or floats.

  • +
  • decimals – number of decimal places to consider. Defaluts to 2. +If integers, set to 0. If set to -infer-, it will remove the zeros +and consider up to the fifth decimal place to the right, but will +loose performance.

  • +
  • sign – tells which portion of the data to consider. ‘pos’: only the positive +entries; ‘neg’: only negative entries; ‘all’: all entries but zeros. +Defaults to ‘all’.

  • +
  • verbose – tells the number of registries that are being subjected to +the analysis and returns tha analysis DataFrame sorted by the +highest Z score down. Defaults to True.

  • +
  • MAD – calculates the Mean Absolute Difference between the +found and the expected distributions; defaults to False.

  • +
  • confidence – confidence level to draw lower and upper limits when +plotting and to limit the top deviations to show. Defaults to None.

  • +
  • high_Z – chooses which Z scores to be used when displaying results, +according to the confidence level chosen. Defaluts to ‘pos’, +which will highlight only values higher than the expexted +frequencies; ‘all’ will highlight both extremes (positive and +negative); and an integer, which will use the first n entries, +positive and negative, regardless of whether Z is higher than +the confidence or not.

  • +
  • limit_N – sets a limit to N as the sample size for the calculation of +the Z scores if the sample is too big. Defaults to None.

  • +
  • MSE – calculates the Mean Square Error of the sample; defaults to +False.

  • +
  • chi_square – calculates the chi_square statistic of the sample and +compares it with a critical value, according to the confidence +level chosen and the series’s degrees of freedom. Defaults to +False. Requires confidence != None.

  • +
  • KS – calculates the Kolmogorov-Smirnov test, comparing the cumulative +distribution of the sample with the Benford’s, according to the +confidence level chosen. Defaults to False. Requires confidence +!= None.

  • +
  • show_plot – draws the test plot.

  • +
  • save_plot – string with the path/name of the file in which the generated +plot will be saved. Uses matplotlib.pyplot.savefig(). File format +is infered by the file name extension. Only available when +plot=True.

  • +
  • save_plot_kwargs – dict with any of the kwargs accepted by +matplotlib.pyplot.savefig() +https://matplotlib.org/api/_as_gen/matplotlib.pyplot.savefig.html +Only available when plot=True and save_plot is a string with the +figure file path/name.

  • +
+
+
Returns
+

+
DataFrame with the Expected and Found proportions, and the Z scores of

the differences if the confidence is not None.

+
+
+

+
+
+
+ +
+
+benford.benford.last_two_digits(data, decimals=2, sign='all', verbose=True, confidence=None, high_Z='pos', limit_N=None, MAD=False, MSE=False, chi_square=False, KS=False, show_plot=True, save_plot=None, save_plot_kwargs=None, inform=None)[source]
+

Performs the Last Two Digits test on the series of +numbers provided.

+
+
Parameters
+
    +
  • data – sequence of numbers to be evaluated. Must be a numpy 1D array, +a pandas Series or a pandas DataFrame column,with values being +integers or floats.

  • +
  • decimals – number of decimal places to consider. Defaluts to 2. +If integers, set to 0. If set to -infer-, it will remove the zeros +and consider up to the fifth decimal place to the right, but will +loose performance.

  • +
  • sign – tells which portion of the data to consider. ‘pos’: only the positive +entries; ‘neg’: only negative entries; ‘all’: all entries but zeros. +Defaults to ‘all’.

  • +
  • verbose – tells the number of registries that are being subjected to +the analysis and returns tha analysis DataFrame sorted by the +highest Z score down. Defaults to True.

  • +
  • confidence – confidence level to draw lower and upper limits when +plotting and to limit the top deviations to show. Defaults to None.

  • +
  • high_Z – chooses which Z scores to be used when displaying results, +according to the confidence level chosen. Defaluts to ‘pos’, +which will highlight only values higher than the expexted +frequencies; ‘all’ will highlight both extremes (positive and +negative); and an integer, which will use the first n entries, +positive and negative, regardless of whether Z is higher than +the confidence or not.

  • +
  • limit_N – sets a limit to N as the sample size for the calculation of +the Z scores if the sample is too big. Defaults to None.

  • +
  • MAD – calculates the Mean Absolute Difference between the +found and the expected distributions; defaults to False.

  • +
  • MSE – calculates the Mean Square Error of the sample; defaults to +False.

  • +
  • chi_square – calculates the chi_square statistic of the sample and +compares it with a critical value, according to the confidence +level chosen and the series’s degrees of freedom. Defaults to +False. Requires confidence != None.

  • +
  • KS – calculates the Kolmogorov-Smirnov test, comparing the cumulative +distribution of the sample with the Benford’s, according to the +confidence level chosen. Defaults to False. Requires confidence +!= None.

  • +
  • show_plot – draws the test plot.

  • +
  • save_plot – string with the path/name of the file in which the generated +plot will be saved. Uses matplotlib.pyplot.savefig(). File format +is infered by the file name extension. Only available when +plot=True.

  • +
  • save_plot_kwargs – dict with any of the kwargs accepted by +matplotlib.pyplot.savefig() +https://matplotlib.org/api/_as_gen/matplotlib.pyplot.savefig.html +Only available when plot=True and save_plot is a string with the +figure file path/name.

  • +
+
+
Returns
+

+
DataFrame with the Expected and Found proportions, and the Z scores of

the differences if the confidence is not None.

+
+
+

+
+
+
+ +
+
+benford.benford.mantissas(data, report=True, show_plot=True, arc_test=True, save_plot=None, save_plot_kwargs=None, inform=None)[source]
+

Extraxts the mantissas of the records logarithms

+
+
Parameters
+
    +
  • data – sequence to compute mantissas from, numpy 1D array, pandas Series +of pandas DataFrame column.

  • +
  • report – prints the mamtissas mean, variance, skewness and kurtosis +for the sequence studied, along with reference values.

  • +
  • show_plot – plots the ordered mantissas and a line with the expected +inclination. Defaults to True.

  • +
  • arc_test – draws the Arc Test plot. Defaluts to True.

  • +
  • save_plot – string with the path/name of the file in which the generated +plot will be saved. Uses matplotlib.pyplot.savefig(). File format +is infered by the file name extension. Only available when +plot=True.

  • +
  • save_plot_kwargs – dict with any of the kwargs accepted by +matplotlib.pyplot.savefig() +https://matplotlib.org/api/_as_gen/matplotlib.pyplot.savefig.html +Only available when plot=True and save_plot is a string with the +figure file path/name.

  • +
+
+
Returns
+

Series with the data mantissas.

+
+
+
+ +
+
+benford.benford.summation(data, digs=2, decimals=2, sign='all', top=20, verbose=True, show_plot=True, save_plot=None, save_plot_kwargs=None, inform=None)[source]
+

Performs the Summation test. In a Benford series, the sums of the +entries begining with the same digits tends to be the same. +Works only with the First Digits (1, 2 or 3) test.

+
+
Parameters
+
    +
  • digs – tells the first digits to use: 1- first; 2- first two; +3- first three. Defaults to 2.

  • +
  • decimals – number of decimal places to consider. Defaluts to 2. +If integers, set to 0. If set to -infer-, it will remove the zeros +and consider up to the fifth decimal place to the right, but will +loose performance.

  • +
  • top – choses how many top values to show. Defaults to 20.

  • +
  • show_plot – plots the results. Defaults to True.

  • +
  • save_plot – string with the path/name of the file in which the generated +plot will be saved. Uses matplotlib.pyplot.savefig(). File format +is infered by the file name extension. Only available when +plot=True.

  • +
  • save_plot_kwargs – dict with any of the kwargs accepted by +matplotlib.pyplot.savefig() +https://matplotlib.org/api/_as_gen/matplotlib.pyplot.savefig.html +Only available when plot=True and save_plot is a string with the +figure file path/name.

  • +
+
+
Returns
+

+
DataFrame with the Summation test, whether sorted in descending order

(if verbose == True) or not.

+
+
+

+
+
+
+ +
+
+benford.benford.mad(data, test, decimals=2, sign='all', verbose=False)[source]
+

Calculates the Mean Absolute Deviation of the Series

+
+
Parameters
+
    +
  • data – sequence of numbers to be evaluated. Must be a numpy 1D array, +a pandas Series or a pandas DataFrame column, with values being +integers or floats.

  • +
  • test – informs which base test to use for the mad.

  • +
  • decimals – number of decimal places to consider. Defaluts to 2. +If integers, set to 0. If set to -infer-, it will remove the zeros +and consider up to the fifth decimal place to the right, but will +loose performance.

  • +
  • sign – tells which portion of the data to consider. pos: only the positive +entries; neg: only negative entries; all: all entries but zeros. +Defaults to all.

  • +
+
+
Returns
+

the Mean Absolute Deviation of the Series

+
+
Return type
+

float

+
+
+
+ +
+
+benford.benford.mse(data, test, decimals=2, sign='all', verbose=False)[source]
+

Calculates the Mean Squared Error of the Series

+
+
Parameters
+
    +
  • data – sequence of numbers to be evaluated. Must be a numpy 1D array, +a pandas Series or a pandas DataFrame column, with values being +integers or floats.

  • +
  • test – informs which base test to use for the mad.

  • +
  • decimals – number of decimal places to consider. Defaluts to 2. +If integers, set to 0. If set to -infer-, it will remove the zeros +and consider up to the fifth decimal place to the right, but will +loose performance.

  • +
  • sign – tells which portion of the data to consider. pos: only the positive +entries; neg: only negative entries; all: all entries but zeros. +Defaults to all.

  • +
+
+
Returns
+

the Mean Squared Error of the Series

+
+
Return type
+

float

+
+
+
+ +
+
+benford.benford.mad_summ(data, test, decimals=2, sign='all', verbose=False)[source]
+

Calculate the Mean Absolute Deviation of the Summation Test

+
+
Parameters
+
    +
  • data – sequence of numbers to be evaluated. Must be a numpy 1D array, +a pandas Series or a pandas DataFrame column, with values being +integers or floats.

  • +
  • test – informs which base test to use for the summation mad.

  • +
  • decimals – number of decimal places to consider. Defaluts to 2. +If integers, set to 0. If set to -infer-, it will remove the zeros +and consider up to the fifth decimal place to the right, but will +loose performance.

  • +
  • sign – tells which portion of the data to consider. pos: only the positive +entries; neg: only negative entries; all: all entries but zeros. +Defaults to all.

  • +
+
+
Returns
+

the Mean Absolute Deviation of the Summation Test

+
+
Return type
+

float

+
+
+
+ +
+
+benford.benford.rolling_mad(data, test, window, decimals=2, sign='all', show_plot=False, save_plot=None, save_plot_kwargs=None)[source]
+

Applies the MAD to sequential subsets of the records.

+
+
Parameters
+
    +
  • data – sequence of numbers to be evaluated. Must be a numpy 1D array, +a pandas Series or a pandas DataFrame column, with values being +integers or floats.

  • +
  • test – tells which test to use. 1: Fisrt Digits; 2: First Two Digits; +3: First Three Digits; 22: Second Digit; and -2: Last Two Digits.

  • +
  • window – size of the subset to be used.

  • +
  • decimals – number of decimal places to consider. Defaluts to 2. +If integers, set to 0. If set to -infer-, it will remove the zeros +and consider up to the fifth decimal place to the right, but will +loose performance.

  • +
  • sign – tells which portion of the data to consider. pos: only the positive +entries; neg: only negative entries; all: all entries but zeros. +Defaults to all.

  • +
  • show_plot – draws the test plot.

  • +
  • save_plot – string with the path/name of the file in which the generated +plot will be saved. Uses matplotlib.pyplot.savefig(). File format +is infered by the file name extension. Only available when +plot=True.

  • +
  • save_plot_kwargs – dict with any of the kwargs accepted by +matplotlib.pyplot.savefig() +https://matplotlib.org/api/_as_gen/matplotlib.pyplot.savefig.html +Only available when plot=True and save_plot is a string with the +figure file path/name.

  • +
+
+
Returns
+

Series with sequentially computed MADs.

+
+
+
+ +
+
+benford.benford.rolling_mse(data, test, window, decimals=2, sign='all', show_plot=False, save_plot=None, save_plot_kwargs=None)[source]
+

Applies the MSE to sequential subsets of the records.

+
+
Parameters
+
    +
  • data – sequence of numbers to be evaluated. Must be a numpy 1D array, +a pandas Series or a pandas DataFrame column, with values being +integers or floats.

  • +
  • test – tells which test to use. 1: Fisrt Digits; 2: First Two Digits; +3: First Three Digits; 22: Second Digit; and -2: Last Two Digits.

  • +
  • window – size of the subset to be used.

  • +
  • decimals – number of decimal places to consider. Defaluts to 2. +If integers, set to 0. If set to -infer-, it will remove the zeros +and consider up to the fifth decimal place to the right, but will +loose performance.

  • +
  • sign – tells which portion of the data to consider. pos: only the positive +entries; neg: only negative entries; all: all entries but zeros. +Defaults to all.

  • +
  • show_plot – draws the test plot.

  • +
  • save_plot – string with the path/name of the file in which the generated +plot will be saved. Uses matplotlib.pyplot.savefig(). File format +is infered by the file name extension. Only available when +plot=True.

  • +
  • save_plot_kwargs – dict with any of the kwargs accepted by +matplotlib.pyplot.savefig() +https://matplotlib.org/api/_as_gen/matplotlib.pyplot.savefig.html +Only available when plot=True and save_plot is a string with the +figure file path/name.

  • +
+
+
Returns
+

Series with sequentially computed MSEs.

+
+
+
+ +
+
+benford.benford.duplicates(data, top_Rep=20, verbose=True, inform=None)[source]
+

Performs a duplicates test and maps the duplicates count in descending +order.

+
+
Parameters
+
    +
  • data – sequence to take the duplicates from. pandas Series or +numpy Ndarray.

  • +
  • verbose – tells how many duplicated entries were found and prints the +top numbers according to the top_Rep argument. Defaluts to True.

  • +
  • top_Rep – chooses how many duplicated entries will be +shown withe the top repititions. int or None. Defaluts to 20. +If None, returns al the ordered repetitions.

  • +
+
+
Returns
+

DataFrame with the duplicated records and their respective counts

+
+
Raises
+

ValueError – if the top_Rep arg is not int or None.

+
+
+
+ +
+
+benford.benford.second_order(data, test, decimals=2, sign='all', verbose=True, MAD=False, confidence=None, high_Z='pos', limit_N=None, MSE=False, show_plot=True, save_plot=None, save_plot_kwargs=None, inform=None)[source]
+

Performs the chosen test after subtracting the ordered sequence by itself. +Hence Second Order.

+
+
Parameters
+
    +
  • data – sequence of numbers to be evaluated. Must be a numpy 1D array, +a pandas Series or a pandas DataFrame column, with values being +integers or floats.

  • +
  • test – the test to be performed - 1 or ‘F1D’: First Digit; 2 or ‘F2D’: +First Two Digits; 3 or ‘F3D’: First three Digits; 22 or ‘SD’: +Second Digits; -2 or ‘L2D’: Last Two Digits.

  • +
  • decimals – number of decimal places to consider. Defaluts to 2. +If integers, set to 0. If set to -infer-, it will remove the zeros +and consider up to the fifth decimal place to the right, but will +loose performance.

  • +
  • sign – tells which portion of the data to consider. pos: only the positive +entries; neg: only negative entries; all: all entries but zeros. +Defaults to all.

  • +
  • verbose – tells the number of registries that are being subjected to +the analysis and returns tha analysis DataFrame sorted by the +highest Z score down. Defaults to True.

  • +
  • MAD – calculates the Mean Absolute Difference between the +found and the expected distributions; defaults to False.

  • +
  • confidence – confidence level to draw lower and upper limits when +plotting and to limit the top deviations to show. Defaults to None.

  • +
  • high_Z – chooses which Z scores to be used when displaying results, +according to the confidence level chosen. Defaluts to ‘pos’, +which will highlight only values higher than the expexted +frequencies; ‘all’ will highlight both extremes (positive and +negative); and an integer, which will use the first n entries, +positive and negative, regardless of whether Z is higher than +the confidence or not.

  • +
  • limit_N – sets a limit to N as the sample size for the calculation of +the Z scores if the sample is too big. Defaults to None.

  • +
  • MSE – calculates the Mean Square Error of the sample; defaults to +False.

  • +
  • chi_square – calculates the chi_square statistic of the sample and +compares it with a critical value, according to the confidence +level chosen and the series’s degrees of freedom. Defaults to +False. Requires confidence != None.

  • +
  • KS – calculates the Kolmogorov-Smirnov test, comparing the cumulative +distribution of the sample with the Benford’s, according to the +confidence level chosen. Defaults to False. Requires confidence +!= None.

  • +
  • show_plot – draws the test plot.

  • +
  • save_plot – string with the path/name of the file in which the generated +plot will be saved. Uses matplotlib.pyplot.savefig(). File format +is infered by the file name extension. Only available when +plot=True.

  • +
  • save_plot_kwargs – dict with any of the kwargs accepted by +matplotlib.pyplot.savefig() +https://matplotlib.org/api/_as_gen/matplotlib.pyplot.savefig.html +Only available when plot=True and save_plot is a string with the +figure file path/name.

  • +
+
+
Returns
+

+
DataFrame of the test chosen, but applied on Second Order pre-

processed data.

+
+
+

+
+
+
+ +
+
+

benford.expected module

+
+
+class benford.expected.First(digs, plot=True, save_plot=None, save_plot_kwargs=None)[source]
+

Bases: pandas.core.frame.DataFrame

+

Holds the expected probabilities of the First, First Two, or +First Three digits according to Benford’s distribution.

+
+
Parameters
+
    +
  • digs – 1, 2 or 3 - tells which of the first digits to consider: +1 for the First Digit, 2 for the First Two Digits and 3 for +the First Three Digits.

  • +
  • plot – option to plot a bar chart of the Expected proportions. +Defaults to True.

  • +
  • save_plot – string with the path/name of the file in which the generated +plot will be saved. Uses matplotlib.pyplot.savefig(). File format +is infered by the file name extension. Only available when +plot=True.

  • +
  • save_plot_kwargs – dict with any of the kwargs accepted by +matplotlib.pyplot.savefig() +https://matplotlib.org/api/_as_gen/matplotlib.pyplot.savefig.html +Only available when plot=True and save_plot is a string with the +figure file path/name.

  • +
+
+
+
+ +
+
+class benford.expected.Second(plot=True, save_plot=None, save_plot_kwargs=None)[source]
+

Bases: pandas.core.frame.DataFrame

+

Holds the expected probabilities of the Second Digits +according to Benford’s distribution.

+
+
Parameters
+
    +
  • plot – option to plot a bar chart of the Expected proportions. +Defaults to True.

  • +
  • save_plot – string with the path/name of the file in which the generated +plot will be saved. Uses matplotlib.pyplot.savefig(). File format +is infered by the file name extension. Only available when +plot=True.

  • +
  • save_plot_kwargs – dict with any of the kwargs accepted by +matplotlib.pyplot.savefig() +https://matplotlib.org/api/_as_gen/matplotlib.pyplot.savefig.html +Only available when plot=True and save_plot is a string with the +figure file path/name.

  • +
+
+
+
+ +
+
+class benford.expected.LastTwo(num=False, plot=True, save_plot=None, save_plot_kwargs=None)[source]
+

Bases: pandas.core.frame.DataFrame

+

Holds the expected probabilities of the Last Two Digits +according to Benford’s distribution.

+
+
Parameters
+
    +
  • plot – option to plot a bar chart of the Expected proportions. +Defaults to True.

  • +
  • save_plot – string with the path/name of the file in which the generated +plot will be saved. Uses matplotlib.pyplot.savefig(). File format +is infered by the file name extension. Only available when +plot=True.

  • +
  • save_plot_kwargs – dict with any of the kwargs accepted by +matplotlib.pyplot.savefig() +https://matplotlib.org/api/_as_gen/matplotlib.pyplot.savefig.html +Only available when plot=True and save_plot is a string with the +figure file path/name.

  • +
+
+
+
+ +
+
+

benford.stats module

+
+
+benford.stats.Z_score(frame, N)[source]
+

Computes the Z statistics for the proportions studied

+
+
Parameters
+
    +
  • frame – DataFrame with the expected proportions and the already calculated +Absolute Diferences between the found and expeccted proportions

  • +
  • N – sample size

  • +
+
+
Returns
+

Series of computed Z scores

+
+
+
+ +
+
+benford.stats.chi_sq(frame, ddf, confidence, verbose=True)[source]
+

Comnputes the chi-square statistic of the found distributions and compares +it with the critical chi-square of such a sample, according to the +confidence level chosen and the degrees of freedom - len(sample) -1.

+
+
Parameters
+
    +
  • frame – DataFrame with Found, Expected and their difference columns.

  • +
  • ddf – Degrees of freedom to consider.

  • +
  • confidence – Confidence level to look up critical value.

  • +
  • verbose – prints the chi-squre result and compares to the critical +chi-square for the sample. Defaults to True.

  • +
+
+
Returns
+

+
The computed Chi square statistic and the critical chi square

(according) to the degrees of freedom and confidence level, +for comparison. None if confidence is None

+
+
+

+
+
+
+ +
+
+benford.stats.chi_sq_2(frame)[source]
+

Computes the chi-square statistic of the found distributions

+
+
Parameters
+

frame – DataFrame with Found, Expected and their difference columns.

+
+
Returns
+

The computed Chi square statistic

+
+
+
+ +
+
+benford.stats.kolmogorov_smirnov(frame, confidence, N, verbose=True)[source]
+

Computes the Kolmogorov-Smirnov test of the found distributions +and compares it with the critical chi-square of such a sample, +according to the confidence level chosen.

+
+
Parameters
+
    +
  • frame – DataFrame with Foud and Expected distributions.

  • +
  • confidence – Confidence level to look up critical value.

  • +
  • N – Sample size

  • +
  • verbose – prints the KS result and the critical value for the sample. +Defaults to True.

  • +
+
+
Returns
+

+
The Suprem, which is the greatest absolute difference between the

Found and the expected proportions, and the Kolmogorov-Smirnov +critical value according to the confidence level, for ccomparison

+
+
+

+
+
+
+ +
+
+benford.stats.kolmogorov_smirnov_2(frame)[source]
+

Computes the Kolmogorov-Smirnov test of the found distributions

+
+
Parameters
+

frame – DataFrame with Foud and Expected distributions.

+
+
Returns
+

+
The Suprem, which is the greatest absolute difference between the

Found end th expected proportions

+
+
+

+
+
+
+ +
+
+benford.stats.mad(frame, test, verbose=True)[source]
+

Computes the Mean Absolute Deviation (MAD) between the found and the +expected proportions.

+
+
Parameters
+
    +
  • frame – DataFrame with the Absolute Deviations already calculated.

  • +
  • test – Test to compute the MAD from (F1D, SD, F2D…)

  • +
  • verbose – prints the MAD result and compares to limit values of +conformity. Defaults to True.

  • +
+
+
Returns
+

+
The Mean of the Absolute Deviations between the found and expected

proportions.

+
+
+

+
+
+
+ +
+
+benford.stats.mse(frame, verbose=True)[source]
+

Computes the test’s Mean Square Error

+
+
Parameters
+
    +
  • frame – DataFrame with the already computed Absolute Deviations between +the found and expected proportions

  • +
  • verbose – Prints the MSE. Defaults to True.

  • +
+
+
Returns
+

Mean of the squared differences between the found and the expected proportions.

+
+
+
+ +
+
+

benford.viz module

+
+
+benford.viz.plot_expected(df, digs, save_plot=None, save_plot_kwargs=None)[source]
+

Plots the Expected Benford Distributions

+
+
Parameters
+
    +
  • df – DataFrame with the Expected Proportions

  • +
  • digs – Test’s digit

  • +
  • save_plot – string with the path/name of the file in which the generated +plot will be saved. Uses matplotlib.pyplot.savefig(). File format +is infered by the file name extension.

  • +
  • save_plot_kwargs – dict with any of the kwargs accepted by +matplotlib.pyplot.savefig() +https://matplotlib.org/api/_as_gen/matplotlib.pyplot.savefig.html

  • +
+
+
+
+ +
+
+benford.viz.plot_digs(df, x, y_Exp, y_Found, N, figsize, conf_Z, text_x=False, save_plot=None, save_plot_kwargs=None)[source]
+

Plots the digits tests results

+
+
Parameters
+
    +
  • df – DataFrame with the data to be plotted

  • +
  • x – sequence to be used in the x axis

  • +
  • y_Exp – sequence of the expected proportions to be used in the y axis +(line)

  • +
  • y_Found – sequence of the found proportions to be used in the y axis +(bars)

  • +
  • N – lenght of sequence, to be used when plotting the confidence levels

  • +
  • figsize – tuple to state the size of the plot figure

  • +
  • conf_Z – Confidence level

  • +
  • save_pic – file path to save figure

  • +
  • text_x – Forces to show all x ticks labels. Defaluts to True.

  • +
  • save_plot – string with the path/name of the file in which the generated +plot will be saved. Uses matplotlib.pyplot.savefig(). File format +is infered by the file name extension.

  • +
  • save_plot_kwargs – dict with any of the kwargs accepted by +matplotlib.pyplot.savefig() +https://matplotlib.org/api/_as_gen/matplotlib.pyplot.savefig.html

  • +
+
+
+
+ +
+
+benford.viz.plot_sum(df, figsize, li, text_x=False, save_plot=None, save_plot_kwargs=None)[source]
+

Plots the summation test results

+
+
Parameters
+
    +
  • df – DataFrame with the data to be plotted

  • +
  • figsize – sets the dimensions of the plot figure

  • +
  • li – value with which to draw the horizontal line

  • +
  • save_plot – string with the path/name of the file in which the generated +plot will be saved. Uses matplotlib.pyplot.savefig(). File format +is infered by the file name extension.

  • +
  • save_plot_kwargs – dict with any of the kwargs accepted by +matplotlib.pyplot.savefig() +https://matplotlib.org/api/_as_gen/matplotlib.pyplot.savefig.html

  • +
+
+
+
+ +
+
+benford.viz.plot_ordered_mantissas(col, figsize=(12, 12), save_plot=None, save_plot_kwargs=None)[source]
+
+
Plots the ordered mantissas and compares them to the expected, straight

line that should be formed in a Benford-cmpliant set.

+
+
+
+
Parameters
+
    +
  • col (Series) – column of mantissas to plot.

  • +
  • figsize (tuple) – sets the dimensions of the plot figure.

  • +
  • save_plot – string with the path/name of the file in which the generated +plot will be saved. Uses matplotlib.pyplot.savefig(). File format +is infered by the file name extension.

  • +
  • save_plot_kwargs – dict with any of the kwargs accepted by +matplotlib.pyplot.savefig() +https://matplotlib.org/api/_as_gen/matplotlib.pyplot.savefig.html

  • +
+
+
+
+ +
+
+benford.viz.plot_mantissa_arc_test(df, gravity_center, grid=True, figsize=12, save_plot=None, save_plot_kwargs=None)[source]
+

Draws thee Mantissa Arc Test after computing X and Y circular coordinates +for every mantissa and the center of gravity for the set

+
+
Parameters
+
    +
  • df (DataFrame) – pandas DataFrame with the mantissas and the X and Y +coordinates.

  • +
  • gravity_center (tuple) – coordinates for plottling the gravity center

  • +
  • grid (bool) – show grid. Defaults to True.

  • +
  • figsize (int) – figure dimensions. No need to be a tuple, since the +figure is a square.

  • +
  • save_plot – string with the path/name of the file in which the generated +plot will be saved. Uses matplotlib.pyplot.savefig(). File format +is infered by the file name extension.

  • +
  • save_plot_kwargs – dict with any of the kwargs accepted by +matplotlib.pyplot.savefig() +https://matplotlib.org/api/_as_gen/matplotlib.pyplot.savefig.html

  • +
+
+
+
+ +
+
+benford.viz.plot_roll_mse(roll_series, figsize, save_plot=None, save_plot_kwargs=None)[source]
+

Shows the rolling MSE plot

+
+
Parameters
+
    +
  • roll_series – pd.Series resultant form rolling mse.

  • +
  • figsize – the figure dimensions.

  • +
  • save_plot – string with the path/name of the file in which the generated +plot will be saved. Uses matplotlib.pyplot.savefig(). File format +is infered by the file name extension.

  • +
  • save_plot_kwargs – dict with any of the kwargs accepted by +matplotlib.pyplot.savefig() +https://matplotlib.org/api/_as_gen/matplotlib.pyplot.savefig.html

  • +
+
+
+
+ +
+
+benford.viz.plot_roll_mad(roll_mad, figsize, save_plot=None, save_plot_kwargs=None)[source]
+

Shows the rolling MAD plot

+
+
Parameters
+
    +
  • roll_mad – pd.Series resultant form rolling mad.

  • +
  • figsize – the figure dimensions.

  • +
  • save_plot – string with the path/name of the file in which the generated +plot will be saved. Uses matplotlib.pyplot.savefig(). File format +is infered by the file name extension.

  • +
  • save_plot_kwargs – dict with any of the kwargs accepted by +matplotlib.pyplot.savefig() +https://matplotlib.org/api/_as_gen/matplotlib.pyplot.savefig.html

  • +
+
+
+
+ +
+
+ + +
+ +
+ + +
+
+ +
+ +
+ + + + + + + + + + + + \ No newline at end of file diff --git a/docs/build/html/genindex.html b/docs/build/html/genindex.html new file mode 100644 index 0000000..3d50a4c --- /dev/null +++ b/docs/build/html/genindex.html @@ -0,0 +1,530 @@ + + + + + + + + + + + + Index — benford_py 0.3.0 documentation + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + +
+ + + +
+ + + + + +
+ +
+ + + + + + + + + + + + + + + + + +
+ +
    + +
  • Docs »
  • + +
  • Index
  • + + +
  • + + + +
  • + +
+ + +
+
+
+
+ + +

Index

+ +
+ A + | B + | C + | D + | F + | K + | L + | M + | N + | P + | R + | S + | T + | U + | V + | Z + +
+

A

+ + + +
+ +

B

+ + + +
+ +

C

+ + + +
+ +

D

+ + + +
+ +

F

+ + + +
+ +

K

+ + + +
+ +

L

+ + + +
+ +

M

+ + + +
+ +

N

+ + +
+ +

P

+ + + +
+ +

R

+ + + +
+ +

S

+ + + +
+ +

T

+ + + +
+ +

U

+ + +
+ +

V

+ + +
+ +

Z

+ + +
+ + + +
+ +
+ + +
+
+ +
+ +
+ + + + + + + + + + + + \ No newline at end of file diff --git a/docs/build/html/index.html b/docs/build/html/index.html new file mode 100644 index 0000000..cfa86e0 --- /dev/null +++ b/docs/build/html/index.html @@ -0,0 +1,232 @@ + + + + + + + + + + + Welcome to benford_py’s documentation! — benford_py 0.3.0 documentation + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + +
+ + + +
+ + + + + +
+ +
+ + + + + + + + + + + + + + + + + +
+ + + + +
+
+
+
+ +
+

Welcome to benford_py’s documentation!

+ +
+
+

Indices and tables

+ +
+

On GitHub

+ + +
+
+ + +
+ +
+ + +
+
+ +
+ +
+ + + + + + + + + + + + \ No newline at end of file diff --git a/docs/build/html/modules.html b/docs/build/html/modules.html new file mode 100644 index 0000000..b9bb6f3 --- /dev/null +++ b/docs/build/html/modules.html @@ -0,0 +1,217 @@ + + + + + + + + + + + benford — benford_py 0.3.0 documentation + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + +
+ + + +
+ + + + + +
+ +
+ + + + + + + + + + + + + + + + + +
+ + + + +
+
+ + + +
+
+ +
+ +
+ + + + + + + + + + + + \ No newline at end of file diff --git a/docs/build/html/objects.inv b/docs/build/html/objects.inv new file mode 100644 index 0000000000000000000000000000000000000000..ebcb4dc5ee1c815e77d02ee0c1433ff584d3ba60 GIT binary patch literal 827 zcmV-B1H}9zAX9K?X>NERX>N99Zgg*Qc_4OWa&u{KZXhxWBOp+6Z)#;@bUGknWo~9~ za%5j{c?u&SR%LQ?X>V>iAPOTORA^-&a%F8{X>Md?av*PJAarPHb0B7EY-J#6b0A}H zZE$jBb8}^6Aa!$TZf78RY-wUH3V7P3n8A+IFc60CeTpEsHUf@&10f^?T_L&>5*N#{ zr>(V)9c-t&%Zb`8-J4w^kiq3dXZIl1=j6LJuNgFUb>tx;J&#HU4YOU-wRIe-E zsrnFF{W4!!zpB=&=3HOD=N<+~XzqRW=EOWHJ_VuZYpyjD*6fr7BfzI5Asnp44$zEQ z@(`SA`T$r^9IOXZhRz8W;gSbl1Zvf)z^;qfmYblw=LJjbL9qARJAn_C<+bcPtwh`(o*Jp%Ddz)| zYOewvjwuEf?rbSvV0eX4Y+1E3>r{xlU9%(CJ!Q)$TFMgOj$RQ5?-|6pENhbm$QvPo0*>@A+SwYs%?>+F&FRykvj&tWN%k(*!?<68fyexXYe zwe?`PY&sqggJ)pbyJ%?g7q)z{Lept8f@CC@nrXLqS0O^^b^kIDymC9SB| zr+d}>PZAfmd=INV7-b~fC$Z)Y=s`YR&CI9a>*^#M$6P4BgBG@hD%dFi>mL68{^P5t z?OQM*+0~0x?2aeGbr0+5MjmgI+bRdn9p#sy$4h5ZT6u^CKb()8uVvjf-~T#H`yc;a F&vt>QjHmzr literal 0 HcmV?d00001 diff --git a/docs/build/html/py-modindex.html b/docs/build/html/py-modindex.html new file mode 100644 index 0000000..898176a --- /dev/null +++ b/docs/build/html/py-modindex.html @@ -0,0 +1,226 @@ + + + + + + + + + + + Python Module Index — benford_py 0.3.0 documentation + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + +
+ + + +
+ + + + + +
+ +
+ + + + + + + + + + + + + + + + + +
+ +
    + +
  • Docs »
  • + +
  • Python Module Index
  • + + +
  • + +
  • + +
+ + +
+
+
+
+ + +

Python Module Index

+ +
+ b +
+ + + + + + + + + + + + + + + + + + + +
 
+ b
+ benford +
    + benford.benford +
    + benford.expected +
    + benford.stats +
    + benford.viz +
+ + +
+ +
+ + +
+
+ +
+ +
+ + + + + + + + + + + + \ No newline at end of file diff --git a/docs/build/html/search.html b/docs/build/html/search.html new file mode 100644 index 0000000..a4148c1 --- /dev/null +++ b/docs/build/html/search.html @@ -0,0 +1,207 @@ + + + + + + + + + + + Search — benford_py 0.3.0 documentation + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + +
+ + + +
+ + + + + +
+ +
+ + + + + + + + + + + + + + + + + +
+ +
    + +
  • Docs »
  • + +
  • Search
  • + + +
  • + + + +
  • + +
+ + +
+
+
+
+ + + + +
+ +
+ +
+ +
+ + +
+
+ +
+ +
+ + + + + + + + + + + + + + + + + \ No newline at end of file diff --git a/docs/build/html/searchindex.js b/docs/build/html/searchindex.js new file mode 100644 index 0000000..a2a55d4 --- /dev/null +++ b/docs/build/html/searchindex.js @@ -0,0 +1 @@ +Search.setIndex({docnames:["api","index","modules"],envversion:{"sphinx.domains.c":1,"sphinx.domains.changeset":1,"sphinx.domains.citation":1,"sphinx.domains.cpp":1,"sphinx.domains.javascript":1,"sphinx.domains.math":2,"sphinx.domains.python":1,"sphinx.domains.rst":1,"sphinx.domains.std":1,"sphinx.ext.viewcode":1,sphinx:56},filenames:["api.rst","index.rst","modules.rst"],objects:{"benford.benford":{Base:[0,1,1,""],Benford:[0,1,1,""],Mantissas:[0,1,1,""],Roll_mad:[0,1,1,""],Roll_mse:[0,1,1,""],Source:[0,1,1,""],Summ:[0,1,1,""],Test:[0,1,1,""],duplicates:[0,4,1,""],first_digits:[0,4,1,""],last_two_digits:[0,4,1,""],mad:[0,4,1,""],mad_summ:[0,4,1,""],mantissas:[0,4,1,""],mse:[0,4,1,""],rolling_mad:[0,4,1,""],rolling_mse:[0,4,1,""],second_digit:[0,4,1,""],second_order:[0,4,1,""],summation:[0,4,1,""]},"benford.benford.Benford":{all_confidences:[0,2,1,""],base:[0,3,1,""],chosen:[0,3,1,""],confidence:[0,3,1,""],data:[0,3,1,""],limit_N:[0,3,1,""],mantissas:[0,2,1,""],sec_order:[0,2,1,""],sign:[0,3,1,""],summation:[0,2,1,""],tests:[0,3,1,""],update_confidence:[0,2,1,""],verbose:[0,3,1,""]},"benford.benford.Mantissas":{arc_test:[0,2,1,""],data:[0,3,1,""],report:[0,2,1,""],show_plot:[0,2,1,""],stats:[0,3,1,""]},"benford.benford.Roll_mad":{show_plot:[0,2,1,""],test:[0,3,1,""]},"benford.benford.Roll_mse":{show_plot:[0,2,1,""]},"benford.benford.Source":{duplicates:[0,2,1,""],first_digits:[0,2,1,""],last_two_digits:[0,2,1,""],mantissas:[0,2,1,""],second_digit:[0,2,1,""],summation:[0,2,1,""],verbose:[0,3,1,""]},"benford.benford.Summ":{MAD:[0,3,1,""],confidence:[0,3,1,""],report:[0,2,1,""],show_plot:[0,2,1,""]},"benford.benford.Test":{KS:[0,3,1,""],MAD:[0,3,1,""],N:[0,3,1,""],chi_square:[0,3,1,""],confidence:[0,3,1,""],critical_values:[0,2,1,""],ddf:[0,3,1,""],digs:[0,3,1,""],report:[0,2,1,""],sec_order:[0,3,1,""],show_plot:[0,2,1,""],update_confidence:[0,2,1,""]},"benford.expected":{First:[0,1,1,""],LastTwo:[0,1,1,""],Second:[0,1,1,""]},"benford.stats":{Z_score:[0,4,1,""],chi_sq:[0,4,1,""],chi_sq_2:[0,4,1,""],kolmogorov_smirnov:[0,4,1,""],kolmogorov_smirnov_2:[0,4,1,""],mad:[0,4,1,""],mse:[0,4,1,""]},"benford.viz":{plot_digs:[0,4,1,""],plot_expected:[0,4,1,""],plot_mantissa_arc_test:[0,4,1,""],plot_ordered_mantissas:[0,4,1,""],plot_roll_mad:[0,4,1,""],plot_roll_mse:[0,4,1,""],plot_sum:[0,4,1,""]},benford:{benford:[0,0,0,"-"],expected:[0,0,0,"-"],stats:[0,0,0,"-"],viz:[0,0,0,"-"]}},objnames:{"0":["py","module","Python module"],"1":["py","class","Python class"],"2":["py","method","Python method"],"3":["py","attribute","Python attribute"],"4":["py","function","Python function"]},objtypes:{"0":"py:module","1":"py:class","2":"py:method","3":"py:attribute","4":"py:function"},terms:{"case":0,"class":0,"default":0,"float":0,"function":0,"int":0,"new":0,"return":0,"true":0,The:0,Uses:0,_as_gen:0,about:0,absolut:0,accept:0,accord:0,add:0,after:0,aldo:0,all:0,all_confid:0,along:0,alreadi:0,also:0,analys:0,analysi:0,ani:0,anoth:0,api:0,appli:0,applic:0,arc:0,arc_test:0,arg:0,argument:0,arrai:0,atribut:0,attribut:0,avail:0,axi:0,bar:0,base:0,befor:0,beford:0,begin:0,being:0,benford:1,between:0,big:0,bool:0,both:0,calcul:0,call:0,can:0,capabl:0,ccomparison:0,center:0,chart:0,check:0,chi:0,chi_sq:0,chi_sq_2:0,chi_squar:0,choic:0,choos:0,chose:0,chosen:0,circl:0,circular:0,cmpliant:0,col:0,column:0,comnput:0,compar:0,comparison:0,compliant:0,comput:0,conf_z:0,confid:0,conform:0,consid:0,content:1,coordin:0,core:0,count:0,creat:0,critic:0,critical_valu:0,cumput:0,cumul:0,current:0,data:0,datafram:0,ddf:0,decim:0,defalut:0,defaut:0,degre:0,descend:0,deviat:0,dict:0,dictionari:0,difer:0,differ:0,dig:0,digit:0,dimens:0,discard:0,displai:0,distribut:0,down:0,draw:0,duplic:0,each:0,element:0,end:0,entri:0,equal:0,error:0,especif:0,evalu:0,everi:0,expecct:0,expect:[1,2],expext:0,extens:0,extraxt:0,extrem:0,f1d:0,f2d:0,f3d:0,fals:0,fifth:0,figsiz:0,figur:0,file:0,first:0,first_digit:0,fisrt:0,follow:0,forc:0,form:0,format:0,foud:0,found:0,frame:0,freedom:0,frequenc:0,from:0,furst:0,futur:0,gener:0,get:0,give:0,given:0,graviti:0,gravity_cent:0,greatest:0,grid:0,hand:0,handl:0,has:0,have:0,henc:0,high_diff:0,high_z:0,higher:0,highest:0,highlight:0,hold:0,horizont:0,how:0,html:0,http:0,inclin:0,includ:0,index:1,infer:0,inform:0,initi:0,input:0,instanc:0,integ:0,intern:0,its:0,itself:0,keep:0,kolmogorov:0,kolmogorov_smirnov:0,kolmogorov_smirnov_2:0,kurtosi:0,kwarg:0,l2d:0,label:0,last:0,last_two_digit:0,lasttwo:0,len:0,lenght:0,level:0,like:0,limit:0,limit_n:0,line:0,list:0,logarithm:0,look:0,loos:0,lower:0,mad:0,mad_summ:0,mamtissa:0,mani:0,mantissa:0,map:0,matplotlib:0,mean:0,method:0,minu:0,modul:[1,2],mse:0,must:0,name:0,ndarrai:0,need:0,neg:0,new_conf:0,none:0,num:0,number:0,numer:0,numpi:0,obj:0,object:0,occurr:0,ocurr:0,one:0,onli:0,option:0,order:0,org:0,origin:0,other:0,output:0,packag:2,page:1,panda:0,paramet:0,path:0,perform:0,place:0,plot:0,plot_dig:0,plot_expect:0,plot_mantissa_arc_test:0,plot_ordered_mantissa:0,plot_roll_mad:0,plot_roll_ms:0,plot_sum:0,plottl:0,popul:0,portion:0,pos:0,posit:0,pre:0,prepar:0,print:0,probabl:0,process:0,produc:0,properti:0,proport:0,provid:0,pyplot:0,rais:0,raw:0,receiv:0,record:0,reduc:0,refer:0,regardless:0,registri:0,relev:0,remov:0,repetit:0,repitit:0,report:0,represent:0,requir:0,respect:0,respectt:0,result:0,ret_df:0,right:0,roll:0,roll_mad:0,roll_ms:0,roll_seri:0,rolling_mad:0,rolling_ms:0,run:0,same:0,sampl:0,save:0,save_p:0,save_plot:0,save_plot_kwarg:0,savefig:0,scatter:0,score:0,search:1,sec_ord:0,second:0,second_digit:0,second_ord:0,select:0,separ:0,sequenc:0,sequenti:0,seri:0,set:0,should:0,show:0,show_plot:0,shown:0,sign:0,simpl:0,sinc:0,size:0,skew:0,smirnov:0,some:0,sort:0,sourc:0,squar:0,squre:0,stat:[1,2],state:0,statist:0,str:0,straight:0,string:0,studi:0,subclass:0,subject:0,subset:0,subtract:0,sum:0,summ:0,summat:0,suprem:0,take:0,tell:0,tend:0,termin:0,test:0,tet:0,text_x:0,tha:0,than:0,thee:0,them:0,thi:0,three:0,through:0,tick:0,too:0,top:0,top_rep:0,track:0,transform:0,tupl:0,two:0,type:0,typeerror:0,updat:0,update_confid:0,upper:0,use:0,used:0,usual:0,valu:0,valueerror:0,varianc:0,verbos:0,viz:[1,2],well:0,were:0,when:0,whether:0,which:0,window:0,withe:0,work:0,y_exp:0,y_found:0,z_score:0,zero:0},titles:["benford package","Welcome to benford_py\u2019s documentation!","benford"],titleterms:{benford:[0,2],benford_pi:1,demo:1,document:1,expect:0,github:1,indic:1,jupyt:1,modul:0,notebook:1,packag:[0,1],stat:0,tabl:1,viz:0,welcom:1}}) \ No newline at end of file
+ + + +
+ + + + + +
+ +
+ + + + + + + + + + + + + + + + + +
+ + + + +
+
+
+
+ +

Source code for benford.benford

+import warnings
+from pandas import Series, DataFrame
+from numpy import arange, log10, ones, abs, cos, sin, pi, mean
+from .constants import confs, digs_dict, sec_order_dict, rev_digs, names, \
+    mad_dict, crit_chi2, KS_crit
+from .checks import _check_digs_, _check_confidence_, _check_test_, \
+    _check_num_array_, _check_high_Z_
+from .utils import _set_N_, input_data, prepare, \
+    subtract_sorted, prep_to_roll, mad_to_roll, mse_to_roll, \
+    get_mantissas
+from .expected import First, Second, LastTwo, _test_
+from .viz import _get_plot_args, plot_digs, plot_sum, plot_ordered_mantissas,\
+    plot_mantissa_arc_test, plot_roll_mse, plot_roll_mad
+from .reports import _inform_, _report_mad_, _report_test_, _deprecate_inform_,\
+    _report_mantissa_
+from .stats import Z_score, chi_sq, chi_sq_2, kolmogorov_smirnov,\
+    kolmogorov_smirnov_2
+
+
+
[docs]class Base(DataFrame): + """Internalizes and prepares the data for Analysis. + + Args: + data: sequence of numbers to be evaluated. Must be a numpy 1D array, + a pandas Series or a pandas DataFrame column, with values being + integers or floats. + decimals: number of decimal places to consider. Defaluts to 2. + If integers, set to 0. If set to -infer-, it will remove the zeros + and consider up to the fifth decimal place to the right, but will + loose performance. + sign: tells which portion of the data to consider. pos: only the positive + entries; neg: only negative entries; all: all entries but zeros. + Defaults to all.` + + Raises: + TypeError: if not receiving `int` or `float` as input. + """ + + def __init__(self, data, decimals, sign='all', sec_order=False): + + DataFrame.__init__(self, {'seq': data}) + + if (self.seq.dtypes != 'float64') & (self.seq.dtypes != 'int64'): + raise TypeError("The sequence dtype was not pandas int64 nor " + "float64. Convert it to whether int of float, " + "and try again.") + + if sign == 'all': + self.seq = self.seq.loc[self.seq != 0] + elif sign == 'pos': + self.seq = self.seq.loc[self.seq > 0] + else: + self.seq = self.seq.loc[self.seq < 0] + + self.dropna(inplace=True) + + ab = self.seq.abs() + + if self.seq.dtypes == 'int64': + self['ZN'] = ab + else: + if decimals == 'infer': + self['ZN'] = ab.astype(str).str\ + .replace('.', '')\ + .str.lstrip('0')\ + .str[:5].astype(int) + else: + self['ZN'] = (ab * (10 ** decimals)).astype(int) + # First digits + for col in ['F1D', 'F2D', 'F3D']: + temp = self.ZN.loc[self.ZN >= 10 ** (rev_digs[col] - 1)] + self[col] = (temp // 10 ** ((log10(temp).astype(int)) - + (rev_digs[col] - 1))) + # fill NANs with -1, which is a non-usable value for digits, + # to be discarded later. + self[col] = self[col].fillna(-1).astype(int) + # Second digit + temp_sd = self.loc[self.ZN >= 10] + self['SD'] = (temp_sd.ZN // 10**((log10(temp_sd.ZN)).astype(int) - + 1)) % 10 + self['SD'] = self['SD'].fillna(-1).astype(int) + # Last two digits + temp_l2d = self.loc[self.ZN >= 1000] + self['L2D'] = temp_l2d.ZN % 100 + self['L2D'] = self['L2D'].fillna(-1).astype(int)
+ + +
[docs]class Test(DataFrame): + """Transforms the original number sequence into a DataFrame reduced + by the ocurrences of the chosen digits, creating other computed + columns + + Args: + base: The Base object with the data prepared for Analysis + digs: Tells which test to perform: 1: first digit; 2: first two digits; + 3: furst three digits; 22: second digit; -2: last two digits. + confidence: confidence level to draw lower and upper limits when + plotting and to limit the top deviations to show. + limit_N: sets a limit to N as the sample size for the calculation of + the Z scores if the sample is too big. Defaults to None. + + Attributes: + N: Number of records in the sample to consider in computations + ddf: Degrees of Freedom to look up for the critical chi-square value + chi_square: Chi-square statistic for the given test + KS: Kolmogorov-Smirnov statistic for the given test + MAD: Mean Absolute Deviation for the given test + confidence: Confidence level to consider when setting some critical values + digs (int): numerical representation of the test at hand. 1: F1D; 2: F2D; + 3: F3D; 22: SD; -2: L2D. + sec_order (bool): True if the test is a Second Order one + """ + + def __init__(self, base, digs, confidence, limit_N=None, sec_order=False): + # create a separated Expected distributions object + super(Test, self).__init__(_test_(digs)) + # create column with occurrences of the digits in the base + self['Counts'] = base[digs_dict[digs]].value_counts() + # create column with relative frequencies + self['Found'] = base[digs_dict[digs]].value_counts(normalize=True) + self.fillna(0, inplace=True) + # create column with absolute differences + self['Dif'] = self.Found - self.Expected + self['AbsDif'] = self.Dif.abs() + self.N = _set_N_(len(base), limit_N) + self['Z_score'] = Z_score(self, self.N) + self.ddf = len(self) - 1 + self.chi_square = chi_sq_2(self) + self.KS = kolmogorov_smirnov_2(self) + self.MAD = self.AbsDif.mean() + self.MSE = (self.AbsDif ** 2).mean() + self.confidence = confidence + self.digs = digs + self.sec_order = sec_order + + if sec_order: + self.name = names[sec_order_dict[digs]] + else: + self.name = names[digs_dict[digs]] + +
[docs] def update_confidence(self, new_conf, check=True): + """Sets a new confidence level for the Benford object, so as to be used to + produce critical values for the tests + + Args: + new_conf: new confidence level to draw lower and upper limits when + plotting and to limit the top deviations to show, as well as to + calculate critical values for the tests' statistics. + check: checks the value provided for the confidence. Defaults to True + """ + if check: + self.confidence = _check_confidence_(new_conf) + else: + self.confidence = new_conf
+ + @property + def critical_values(self): + """dict: a dictionary with the critical values for the test at hand, + according to the current confidence level.""" + return {'Z': confs[self.confidence], + 'KS': KS_crit[self.confidence] / (self.N ** 0.5), + 'chi2': crit_chi2[self.ddf][self.confidence], + 'MAD': mad_dict[self.digs] + } + +
[docs] def show_plot(self, save_plot=None, save_plot_kwargs=None): + """Draws the test plot. + + Args: + save_plot: string with the path/name of the file in which the generated + plot will be saved. Uses matplotlib.pyplot.savefig(). File format + is infered by the file name extension. + save_plot_kwargs: dict with any of the kwargs accepted by + matplotlib.pyplot.savefig() + https://matplotlib.org/api/_as_gen/matplotlib.pyplot.savefig.html + Only available when save_plot is a string with the figure file + path/name. + """ + x, figsize, text_x = _get_plot_args(self.digs) + plot_digs(self, x=x, y_Exp=self.Expected, y_Found=self.Found, + N=self.N, figsize=figsize, conf_Z=confs[self.confidence], + text_x=text_x, save_plot=save_plot, save_plot_kwargs=save_plot_kwargs + )
+ +
[docs] def report(self, high_Z='pos', show_plot=True, + save_plot=None, save_plot_kwargs=None): + """Handles the report especific to the test, considering its statistics + and according to the current confidence level. + + Args: + high_Z: chooses which Z scores to be used when displaying results, + according to the confidence level chosen. Defaluts to 'pos', + which will highlight only values higher than the expexted + frequencies; 'all' will highlight both extremes (positive and + negative); and an integer, which will use the first n entries, + positive and negative, regardless of whether Z is higher than + the critical value or not. + show_plot: calls the show_plot method, to draw the test plot + save_plot: string with the path/name of the file in which the generated + plot will be saved. Uses matplotlib.pyplot.savefig(). File format + is infered by the file name extension. Only available when + plot=True. + save_plot_kwargs: dict with any of the kwargs accepted by + matplotlib.pyplot.savefig() + https://matplotlib.org/api/_as_gen/matplotlib.pyplot.savefig.html + Only available when plot=True and save_plot is a string with the + figure file path/name. + """ + high_Z = _check_high_Z_(high_Z) + _report_test_(self, high_Z, self.critical_values) + if show_plot: + self.show_plot(save_plot=save_plot, save_plot_kwargs=save_plot_kwargs)
+ + +
[docs]class Summ(DataFrame): + """Gets the base object and outputs a Summation test object + + Args: + base: The Base object with the data prepared for Analysis + test: The test for which to compute the summation + """ + + def __init__(self, base, test): + super(Summ, self).__init__(base.abs() + .groupby(test)[['seq']] + .sum()) + self['Percent'] = self.seq / self.seq.sum() + self.columns.values[0] = 'Sum' + self.expected = 1 / len(self) + self['AbsDif'] = (self.Percent - self.expected).abs() + self.index = self.index.astype(int) + #: Mean Absolute Deviation for the test + self.MAD = self.AbsDif.mean() + self.MSE = (self.AbsDif ** 2).mean() + #: Confidence level to consider when setting some critical values + self.confidence = None + # (int): numerical representation of the test at hand + self.digs = rev_digs[test] + # (str): the name of the Summation test. + self.name = names[f'{test}_Summ'] + +
[docs] def show_plot(self, save_plot=None, save_plot_kwargs=None): + """Draws the Summation test plot + + Args: + save_plot: string with the path/name of the file in which the generated + plot will be saved. Uses matplotlib.pyplot.savefig(). File format + is infered by the file name extension. + save_plot_kwargs: dict with any of the kwargs accepted by + matplotlib.pyplot.savefig() + https://matplotlib.org/api/_as_gen/matplotlib.pyplot.savefig.html + Only available when save_plot is a string with the figure file + path/name. + """ + figsize=(2 * (self.digs ** 2 + 5), 1.5 * (self.digs ** 2 + 5)) + plot_sum(self, figsize, self.expected, + save_plot=save_plot, save_plot_kwargs=save_plot_kwargs)
+ +
[docs] def report(self, high_diff=None, show_plot=True, + save_plot=None, save_plot_kwargs=None): + """Gives the report on the Summation test. + + Args: + high_diff: Number of records to show after ordering by the absolute + differences between the found and the expected proportions + show_plot: calls the show_plot method, to draw the Summation test plot + save_plot: string with the path/name of the file in which the generated + plot will be saved. Uses matplotlib.pyplot.savefig(). File format + is infered by the file name extension. Only available when + plot=True. + save_plot_kwargs: dict with any of the kwargs accepted by + matplotlib.pyplot.savefig() + https://matplotlib.org/api/_as_gen/matplotlib.pyplot.savefig.html + Only available when plot=True and save_plot is a string with the + figure file path/name. + """ + _report_test_(self, high_diff) + if show_plot: + self.show_plot(save_plot=save_plot, save_plot_kwargs=save_plot_kwargs)
+ + +class Mantissas(object): + """Computes and holds the mantissas of the logarithms of the records + + Args: + data: sequence to compute mantissas from. numpy 1D array, pandas + Series of pandas DataFrame column. + """ + + def __init__(self, data): + + data = Series(_check_num_array_(data)) + data = data.dropna().loc[data != 0].abs() + #: (DataFrame): pandas DataFrame with the mantissas + self.data = DataFrame({'Mantissa': get_mantissas(data.abs())}) + # (dict): Dictionary with the mantissas statistics + self.stats = {'Mean': self.data.Mantissa.mean(), + 'Var': self.data.Mantissa.var(), + 'Skew': self.data.Mantissa.skew(), + 'Kurt': self.data.Mantissa.kurt()} + + def report(self, show_plot=True, save_plot=None, save_plot_kwargs=None): + """Displays the Mantissas test stats. + + Args: + show_plot: shows the Ordered Mantissas plot and the Arc Test plot. + Defaults to True. + save_plot: string with the path/name of the file in which the generated + plot will be saved. Uses matplotlib.pyplot.savefig(). File format + is infered by the file name extension. Only available when + plot=True. + save_plot_kwargs: dict with any of the kwargs accepted by + matplotlib.pyplot.savefig() + https://matplotlib.org/api/_as_gen/matplotlib.pyplot.savefig.html + Only available when plot=True and save_plot is a string with the + figure file path/name. + """ + _report_mantissa_(self.stats) + + if show_plot: + self.show_plot(save_plot=save_plot, save_plot_kwargs=save_plot_kwargs) + self.arc_test(save_plot=save_plot, save_plot_kwargs=save_plot_kwargs) + + def show_plot(self, figsize=(12, 6), save_plot=None, save_plot_kwargs=None): + """Plots the ordered mantissas and a line with the expected + inclination. + + Args: + figsize (tuple): figure size dimensions + save_plot: string with the path/name of the file in which the generated + plot will be saved. Uses matplotlib.pyplot.savefig(). File format + is infered by the file name extension. + save_plot_kwargs: dict with any of the kwargs accepted by + matplotlib.pyplot.savefig() + https://matplotlib.org/api/_as_gen/matplotlib.pyplot.savefig.html + Only available when save_plot is a string with the figure file + path/name. + """ + plot_ordered_mantissas(self.data.Mantissa, figsize=figsize, + save_plot=save_plot, save_plot_kwargs=save_plot_kwargs) + + def arc_test(self, decimals=2, grid=True, figsize=12, + save_plot=None, save_plot_kwargs=None): + """Adds two columns to Mantissas's DataFrame equal to their "X" and "Y" + coordinates, plots its to a scatter plot and calculates the gravity + center of the circle. + + Args: + decimals: number of decimal places for displaying the gravity center. + Defaults to 2. + grid: show grid of the plot. Defaluts to True. + figsize (int): size of the figure to be displayed. Since it is a square, + there is no need to provide a tuple, like is usually the case with + matplotlib. + save_plot: string with the path/name of the file in which the generated + plot will be saved. Uses matplotlib.pyplot.savefig(). File format + is infered by the file name extension. Only available when + plot=True. + save_plot_kwargs: dict with any of the kwargs accepted by + matplotlib.pyplot.savefig() + https://matplotlib.org/api/_as_gen/matplotlib.pyplot.savefig.html + Only available when plot=True and save_plot is a string with the + figure file path/name. + """ + if self.stats.get('gravity_center') is None: + self.data['mant_x'] = cos(2 * pi * self.data.Mantissa) + self.data['mant_y'] = sin(2 * pi * self.data.Mantissa) + self.stats['gravity_center'] = (self.data.mant_x.mean(), + self.data.mant_y.mean()) + + plot_mantissa_arc_test(self.data, self.stats, decimals=decimals, + grid=grid, figsize=figsize, + save_plot=save_plot, save_plot_kwargs=save_plot_kwargs) + + +
[docs]class Benford(object): + """Initializes a Benford Analysis object and computes the proportions for + the digits. The tets dataFrames are atributes, i.e., obj.F1D is the First + Digit DataFrame, the obj.F2D,the First Two Digits one, and so one, F3D for + First Three Digits, SD for Second Digit and L2D for Last Two Digits. + + Args: + data: sequence of numbers to be evaluated. Must be a numpy 1D array, + a pandas Series or a tuple with a pandas DataFrame and the name (str) + of the chosen column. Values must be integers or floats. + decimals: number of decimal places to consider. Defaluts to 2. + If integers, set to 0. If set to -infer-, it will remove the zeros + and consider up to the fifth decimal place to the right, but will + loose performance. + sign: tells which portion of the data to consider. pos: only the positive + entries; neg: only negative entries; all: all entries but zeros. + Defaults to all. + confidence: confidence level to draw lower and upper limits when + plotting and to limit the top deviations to show, as well as to + calculate critical values for the tests' statistics. Defaults to 95. + sec_order: runs the Second Order tests, which are the Benford's tests + performed on the differences between the ordered sample (a value minus + the one before it, and so on). If the original series is Benford- + compliant, this new sequence should aldo follow Beford. The Second + Order can also be called separately, through the method sec_order(). + summation: creates the Summation DataFrames for the First, First Two, and + First Three Digits. The summation tests can also be called separately, + through the method summation(). + limit_N: sets a limit to N as the sample size for the calculation of + the Z scores if the sample is too big. Defaults to None. + verbose: gives some information about the data and the registries used + and discarded for each test. + + Attributes: + data: the raw data provided for the analysis + chosen: the column of the DataFrame to be analysed or the data itself + sign (str): which number sign(s) to include in the analysis + confidence: current confidence level + limit_N (int): sample size to use in computations + verbose (bool): verbose or not + base: the Base, pre-processed object + tests (:obj:`list` of :obj:`str`): keeps track of the tests the + instance has + """ + + def __init__(self, data, decimals=2, sign='all', confidence=95, + mantissas=False, sec_order=False, summation=False, + limit_N=None, verbose=True): + self.data, self.chosen = input_data(data) + self.decimals = decimals + self.sign = sign + self.confidence = _check_confidence_(confidence) + self.limit_N = limit_N + self.verbose = verbose + self.base = Base(self.chosen, decimals, sign) + self.tests = [] + + # Create a DatFrame for each Test + for key, val in digs_dict.items(): + test = Test(self.base.loc[self.base[val] != -1], + digs=key, confidence=self.confidence, + limit_N=self.limit_N) + setattr(self, val, test) + self.tests.append(val) + # dict with the numbers of discarded entries for each test column + self._discarded = {key: val for (key, val) in + zip(digs_dict.values(), + [len(self.base[col].loc[self.base[col] == -1]) + for col in digs_dict.values()])} + + if self.verbose: + print('\n', ' Benford Object Instantiated '.center(50, '#'), '\n') + print(f'Initial sample size: {len(self.chosen)}.\n') + print(f'Test performed on {len(self.base)} registries.\n') + print( + f'Number of discarded entries for each test:\n{self._discarded}') + + if mantissas: + self.mantissas() + + if sec_order: + self.sec_order() + + if summation: + self.summation() + +
[docs] def update_confidence(self, new_conf, tests=None): + """Sets (a) new confidence level(s) for the Benford object, so as to be + used to produce critical values for the tests. + + Args: + new_conf: new confidence level to draw lower and upper limits when + plotting and to limit the top deviations to show, as well as to + calculate critical values for the tests' statistics. + tests (:obj:`list` of :obj:`str`): list of tests names (strings) to + have their confidence updated. If only one, provide a one-element + list, like ['F1D']. Defauts to None, in which case it will use + the instance .test list attribute. + + Raises: + ValueError: if the test argument is not a `list` or `None`. + """ + self.confidence = _check_confidence_(new_conf) + if tests is None: + tests = self.tests + else: + if not isinstance(tests, list): + raise ValueError('tests must be a list or None.') + for test in tests: + try: + getattr(self, test).update_confidence( + self.confidence, check=False) + except AttributeError: + if test in ['Mantissas', 'F1D_Summ', 'F2D_Summ', 'F3D_Summ']: + pass + else: + print( + f"{test} not in Benford instance tests - review test's name.") + pass
+ + @property + def all_confidences(self): + """dict: a dictionary with a confidence level for each computed tests, + when applicable.""" + con_dic = {} + for key in self.tests: + try: + con_dic[key] = getattr(self, key).confidence + except AttributeError: + pass + return con_dic + +
[docs] def mantissas(self): + """Adds a Mantissas object to the tests, with all its statistics and + plotting capabilities. + """ + self.Mantissas = Mantissas(self.base.seq) + self.tests.append('Mantissas') + if self.verbose: + print('\nAdded Mantissas test.')
+ +
[docs] def sec_order(self): + """Runs the Second Order tests, which are the Benford's tests + performed on the differences between the ordered sample (a value minus + the one before it, and so on). If the original series is Benford- + compliant, this new sequence should aldo follow Beford. The Second + Order can also be called separately, through the method sec_order(). + """ + #: Base instance of the differences between the ordered sample + self.base_sec = Base(subtract_sorted(self.chosen), + decimals=self.decimals, sign=self.sign) + for key, val in digs_dict.items(): + test = Test(self.base_sec.loc[self.base_sec[val] != -1], + digs=key, confidence=self.confidence, + limit_N=self.limit_N, sec_order=True) + setattr(self, sec_order_dict[key], test) + self.tests.append(f'{val}_sec') + # No need to populate crit_vals dict, since they are the + # same and do not depend on N + self._discarded_sec = {key: val for (key, val) in zip( + sec_order_dict.values(), + [sum(self.base_sec[col] == -1) for col in + digs_dict.values()])} + if self.verbose: + print(f'\nSecond order tests run in {len(self.base_sec)} ' + 'registries.\n\nNumber of discarded entries for second order' + f' tests:\n{self._discarded_sec}')
+ +
[docs] def summation(self): + """Creates Summation test DataFrames from Base object""" + for test in ['F1D', 'F2D', 'F3D']: + t = f'{test}_Summ' + setattr(self, t, Summ(self.base, test)) + self.tests.append(t) + + if self.verbose: + print('\nAdded Summation DataFrames to F1D, F2D and F3D Tests.')
+ + +
[docs]class Source(DataFrame): + """Prepares the data for Analysis. pandas DataFrame subclass. + + Args: + data: sequence of numbers to be evaluated. Must be a numpy 1D array, + a pandas Series or a pandas DataFrame column, with values being + integers or floats. + decimals: number of decimal places to consider. Defaluts to 2. + If integers, set to 0. If set to -infer-, it will remove the zeros + and consider up to the fifth decimal place to the right, but will + loose performance. + sign: tells which portion of the data to consider. pos: only the positive + entries; neg: only negative entries; all: all entries but zeros. + Defaults to all. + sec_order: choice for the Second Order Test, which cumputes the + differences between the ordered entries before running the Tests. + verbose: tells the number of registries that are being subjected to + the analysis; defaults to True. + + Raises: + ValueError: if the `sign` arg is not in ['all', 'pos', 'neg'] + TypeError: if not receiving `int` or `float` as input. + """ + + def __init__(self, data, decimals=2, sign='all', sec_order=False, + verbose=True, inform=None): + + if sign not in ['all', 'pos', 'neg']: + raise ValueError("The -sign- argument must be " + "'all','pos' or 'neg'.") + + DataFrame.__init__(self, {'seq': data}) + + if self.seq.dtypes != 'float64' and self.seq.dtypes != 'int64': + raise TypeError('The sequence dtype was not pandas int64 nor float64.\n' + 'Convert it to whether int64 of float64, and try again.') + + if sign == 'pos': + self.seq = self.seq.loc[self.seq > 0] + elif sign == 'neg': + self.seq = self.seq.loc[self.seq < 0] + else: + self.seq = self.seq.loc[self.seq != 0] + + self.dropna(inplace=True) + #: (bool): verbose or not + self.verbose = _deprecate_inform_(verbose, inform) + if self.verbose: + print(f"\nInitialized sequence with {len(self)} registries.") + + if sec_order: + self.seq = subtract_sorted(self.seq.copy()) + self.dropna(inplace=True) + self.reset_index(inplace=True) + if verbose: + print('Second Order Test. Initial series reduced ' + f'to {len(self.seq)} entries.') + + ab = self.seq.abs() + + if self.seq.dtypes == 'int64': + self['ZN'] = ab + else: + if decimals == 'infer': + # There is some numerical issue with Windows that required + # implementing it differently (and slower) + self['ZN'] = ab.astype(str)\ + .str.replace('.', '')\ + .str.lstrip('0').str[:5]\ + .astype(int) + else: + self['ZN'] = (ab * (10 ** decimals)).astype(int) + +
[docs] def mantissas(self, report=True, show_plot=True, figsize=(15, 8), + save_plot=None, save_plot_kwargs=None): + """Calculates the mantissas, their mean and variance, and compares them + with the mean and variance of a Benford's sequence. + + Args: + report: prints the mamtissas mean, variance, skewness and kurtosis + for the sequence studied, along with reference values. + show_plot: plots the ordered mantissas and a line with the expected + inclination. Defaults to True. + figsize: tuple that sets the figure dimensions. + save_plot: string with the path/name of the file in which the generated + plot will be saved. Uses matplotlib.pyplot.savefig(). File format + is infered by the file name extension. Only available when + plot=True. + save_plot_kwargs: dict with any of the kwargs accepted by + matplotlib.pyplot.savefig() + https://matplotlib.org/api/_as_gen/matplotlib.pyplot.savefig.html + Only available when plot=True and save_plot is a string with the + figure file path/name. + """ + self['Mant'] = get_mantissas(self.seq.abs()) + if report: + p = self[['seq', 'Mant']] + p = p.loc[p.seq > 0].sort_values('Mant') + print(f"The Mantissas MEAN is {p.Mant.mean()}. Ref: 0.5.") + print(f"The Mantissas VARIANCE is {p.Mant.var()}. Ref: 0.083333.") + print(f"The Mantissas SKEWNESS is {p.Mant.skew()}. \tRef: 0.") + print(f"The Mantissas KURTOSIS is {p.Mant.kurt()}. \tRef: -1.2.") + + if show_plot: + plot_ordered_mantissas(self.Mant, figsize=figsize, + save_plot=save_plot, save_plot_kwargs=save_plot_kwargs)
+ +
[docs] def first_digits(self, digs, confidence=None, high_Z='pos', + limit_N=None, MAD=False, MSE=False, chi_square=False, + KS=False, show_plot=True, save_plot=None, save_plot_kwargs=None, + simple=False, ret_df=False): + """Performs the Benford First Digits test with the series of + numbers provided, and populates the mapping dict for future + selection of the original series. + + Args: + digs: number of first digits to consider. Must be 1 (first digit), + 2 (first two digits) or 3 (first three digits). + verbose: tells the number of registries that are being subjected to + the analysis; defaults to True + digs: number of first digits to consider. Must be 1 (first digit), + 2 (first two digits) or 3 (first three digits). + confidence: confidence level to draw lower and upper limits when + plotting and to limit the top deviations to show, as well as to + calculate critical values for the tests' statistics. Defaults to None. + high_Z: chooses which Z scores to be used when displaying results, + according to the confidence level chosen. Defaluts to 'pos', + which will highlight only values higher than the expexted + frequencies; 'all' will highlight both extremes (positive and + negative); and an integer, which will use the first n entries, + positive and negative, regardless of whether Z is higher than + the confidence or not. + limit_N: sets a limit to N as the sample size for the calculation of + the Z scores if the sample is too big. Defaults to None. + MAD: calculates the Mean Absolute Difference between the + found and the expected distributions; defaults to False. + MSE: calculates the Mean Square Error of the sample; defaults to + False. + show_plot: draws the test plot. Defaults to True. + save_plot: string with the path/name of the file in which the generated + plot will be saved. Uses matplotlib.pyplot.savefig(). File format + is infered by the file name extension. Only available when + plot=True. + save_plot_kwargs: dict with any of the kwargs accepted by + matplotlib.pyplot.savefig() + https://matplotlib.org/api/_as_gen/matplotlib.pyplot.savefig.html + Only available when plot=True and save_plot is a string with the + figure file path/name. + ret_df: returns the test DataFrame. Defaults to False. True if run by + the test function. + + Returns: + DataFrame with the Expected and Found proportions, and the Z scores of + the differences + """ + # Check on the possible values for confidence levels + confidence = _check_confidence_(confidence) + # Check on possible digits + _check_test_(digs) + + temp = self.loc[self.ZN >= 10 ** (digs - 1)] + temp[digs_dict[digs]] = (temp.ZN // 10 ** ((log10(temp.ZN).astype( + int)) - (digs - 1))).astype( + int) + n, m = 10 ** (digs - 1), 10 ** (digs) + x = arange(n, m) + + if simple: + self.verbose = False + show_plot = False + df = prepare(temp[digs_dict[digs]], digs, limit_N=limit_N, + simple=True) + else: + N, df = prepare(temp[digs_dict[digs]], digs, limit_N=limit_N, + simple=False) + + if self.verbose: + print(f"\nTest performed on {len(temp)} registries.\n" + f"Discarded {len(self) - len(temp)} records < {10 ** (digs - 1)}" + " after preparation.") + if confidence is not None: + _inform_(df, high_Z=high_Z, conf=confs[confidence]) + + # Mean absolute difference + if MAD: + self.MAD = df.AbsDif.mean() + if self.verbose: + _report_mad_(digs, self.MAD) + + # Mean Square Error + if MSE: + self.MSE = (df.AbsDif ** 2).mean() + + # Chi-square statistic + if chi_square: + self.chi_square = chi_sq(df, ddf=len(df) - 1, + confidence=confidence, + verbose=self.verbose) + # KS test + if KS: + self.KS = kolmogorov_smirnov(df, confidence=confidence, N=len(temp), + verbose=self.verbose) + + # Plotting the expected frequncies (line) against the found ones(bars) + if show_plot: + plot_digs(df, x=x, y_Exp=df.Expected, y_Found=df.Found, N=N, + figsize=(2 * (digs ** 2 + 5), 1.5 * (digs ** 2 + 5)), + conf_Z=confs[confidence], save_plot=save_plot, + save_plot_kwargs=save_plot_kwargs) + if ret_df: + return df
+ +
[docs] def second_digit(self, confidence=None, high_Z='pos', + limit_N=None, MAD=False, MSE=False, chi_square=False, + KS=False, show_plot=True, save_plot=None, save_plot_kwargs=None, + simple=False, ret_df=False): + """Performs the Benford Second Digit test with the series of + numbers provided. + + Args: + verbose: tells the number of registries that are being subjected to + the analysis; defaults to True + MAD: calculates the Mean Absolute Difference between the + found and the expected distributions; defaults to False. + confidence: confidence level to draw lower and upper limits when + plotting and to limit the top deviations to show, as well as to + calculate critical values for the tests' statistics. Defaults to None. + high_Z: chooses which Z scores to be used when displaying results, + according to the confidence level chosen. Defaluts to 'pos', + which will highlight only values higher than the expexted + frequencies; 'all' will highlight both extremes (positive and + negative); and an integer, which will use the first n entries, + positive and negative, regardless of whether Z is higher than + the confidence or not. + limit_N: sets a limit to N as the sample size for the calculation of + the Z scores if the sample is too big. Defaults to None. + MSE: calculates the Mean Square Error of the sample; defaults to + False. + show_plot: draws the test plot. + save_plot: string with the path/name of the file in which the generated + plot will be saved. Uses matplotlib.pyplot.savefig(). File format + is infered by the file name extension. Only available when + plot=True. + save_plot_kwargs: dict with any of the kwargs accepted by + matplotlib.pyplot.savefig() + https://matplotlib.org/api/_as_gen/matplotlib.pyplot.savefig.html + Only available when plot=True and save_plot is a string with the + figure file path/name. + ret_df: returns the test DataFrame. Defaults to False. True if run by + the test function. + + Returns: + DataFrame with the Expected and Found proportions, and the Z scores of + the differences + """ + confidence = _check_confidence_(confidence) + + conf = confs[confidence] + + temp = self.loc[self.ZN >= 10, :] + temp['SD'] = (temp.ZN // 10 ** ((log10(temp.ZN)).astype( + int) - 1)) % 10 + + if simple: + self.verbose = False + show_plot = False + df = prepare(temp['SD'], 22, limit_N=limit_N, simple=True) + else: + N, df = prepare(temp['SD'], 22, limit_N=limit_N, simple=False) + + if self.verbose: + print(f"\nTest performed on {len(temp)} registries.\nDiscarded " + f"{len(self) - len(temp)} records < 10 after preparation.") + if confidence is not None: + _inform_(df, high_Z, conf) + + # Mean absolute difference + if MAD: + self.MAD = df.AbsDif.mean() + if self.verbose: + _report_mad_(22, self.MAD) + # Mean Square Error + if MSE: + self.MSE = (df.AbsDif ** 2).mean() + + # Chi-square statistic + if chi_square: + self.chi_square = chi_sq(df, ddf=9, confidence=confidence, + verbose=self.verbose) + # KS test + if KS: + self.KS = kolmogorov_smirnov(df, confidence=confidence, N=len(temp), + verbose=self.verbose) + + # Plotting the expected frequncies (line) against the found ones(bars) + if show_plot: + plot_digs(df, x=arange(0, 10), y_Exp=df.Expected, + y_Found=df.Found, N=N, figsize=(10, 6), conf_Z=conf, + save_plot=save_plot, save_plot_kwargs=save_plot_kwargs) + if ret_df: + return df
+ +
[docs] def last_two_digits(self, confidence=None, high_Z='pos', + limit_N=None, MAD=False, MSE=False, chi_square=False, + KS=False, show_plot=True, save_plot=None, save_plot_kwargs=None, + simple=False, ret_df=False): + """Performs the Benford Last Two Digits test with the series of + numbers provided. + + Args: + verbose: tells the number of registries that are being subjected to + the analysis; defaults to True + MAD: calculates the Mean Absolute Difference between the + found and the expected distributions; defaults to False. + confidence: confidence level to draw lower and upper limits when + plotting and to limit the top deviations to show, as well as to + calculate critical values for the tests' statistics. Defaults to None. + high_Z: chooses which Z scores to be used when displaying results, + according to the confidence level chosen. Defaluts to 'pos', + which will highlight only values higher than the expexted + frequencies; 'all' will highlight both extremes (positive and + negative); and an integer, which will use the first n entries, + positive and negative, regardless of whether Z is higher than + the confidence or not. + limit_N: sets a limit to N as the sample size for the calculation of + the Z scores if the sample is too big. Defaults to None. + MSE: calculates the Mean Square Error of the sample; defaults to + False. + show_plot: draws the test plot. + save_plot: string with the path/name of the file in which the generated + plot will be saved. Uses matplotlib.pyplot.savefig(). File format + is infered by the file name extension. Only available when + plot=True. + save_plot_kwargs: dict with any of the kwargs accepted by + matplotlib.pyplot.savefig() + https://matplotlib.org/api/_as_gen/matplotlib.pyplot.savefig.html + Only available when plot=True and save_plot is a string with the + figure file path/name. + + Returns: + DataFrame with the Expected and Found proportions, and the Z scores of + the differences + """ + confidence = _check_confidence_(confidence) + conf = confs[confidence] + + temp = self.loc[self.ZN >= 1000] + temp['L2D'] = temp.ZN % 100 + + if simple: + self.verbose = False + show_plot = False + df = prepare(temp['L2D'], -2, limit_N=limit_N, simple=True) + else: + N, df = prepare(temp['L2D'], -2, limit_N=limit_N, simple=False) + + if self.verbose: + print(f"\nTest performed on {len(temp)} registries.\n\nDiscarded " + f"{len(self) - len(temp)} records < 1000 after preparation") + if confidence is not None: + _inform_(df, high_Z, conf) + + # Mean absolute difference + if MAD: + self.MAD = df.AbsDif.mean() + if self.verbose: + _report_mad_(-2, self.MAD) + # Mean Square Error + if MSE: + self.MSE = (df.AbsDif ** 2).mean() + + # Chi-square statistic + if chi_square: + self.chi_square = chi_sq(df, ddf=99, confidence=confidence, + verbose=self.verbose) + # KS test + if KS: + self.KS = kolmogorov_smirnov(df, confidence=confidence, N=len(temp), + verbose=self.verbose) + + # Plotting expected frequencies (line) versus found ones (bars) + if show_plot: + plot_digs(df, x=arange(0, 100), y_Exp=df.Expected, + y_Found=df.Found, N=N, figsize=(15, 5), + conf_Z=conf, text_x=True, save_plot=save_plot, + save_plot_kwargs=save_plot_kwargs) + if ret_df: + return df
+ +
[docs] def summation(self, digs=2, top=20, show_plot=True, save_plot=None, + save_plot_kwargs=None, ret_df=False): + """Performs the Summation test. In a Benford series, the sums of the + entries begining with the same digits tends to be the same. + + Args: + digs: tells the first digits to use. 1- first; 2- first two; + 3- first three. Defaults to 2. + top: choses how many top values to show. Defaults to 20. + show_plot: plots the results. Defaults to True. + save_plot: string with the path/name of the file in which the generated + plot will be saved. Uses matplotlib.pyplot.savefig(). File format + is infered by the file name extension. Only available when + plot=True. + save_plot_kwargs: dict with any of the kwargs accepted by + matplotlib.pyplot.savefig() + https://matplotlib.org/api/_as_gen/matplotlib.pyplot.savefig.html + Only available when plot=True and save_plot is a string with the + figure file path/name. + + Returns: + DataFrame with the Expected and Found proportions, and their + absolute differences + """ + _check_digs_(digs) + + if digs == 1: + top = 9 + # Call the dict for F1D, F2D, F3D + d = digs_dict[digs] + if d not in self.columns: + self[d] = self.ZN.astype(str).str[:digs].astype(int) + # Call the expected proportion according to digs + li = 1. / (9 * (10 ** (digs - 1))) + + df = self.groupby(d).sum() + # s.drop(0, inplace=True) + df['Percent'] = df.ZN / df.ZN.sum() + df.columns.values[1] = 'Summ' + df = df[['Summ', 'Percent']] + df['AbsDif'] = (df.Percent - li).abs() + + if self.verbose: + # N = len(self) + print(f"\nTest performed on {len(self)} registries.\n") + print(f"The top {top} diferences are:\n") + print(df[:top]) + + if show_plot: + plot_sum(df, figsize=( + 2 * (digs ** 2 + 5), 1.5 * (digs ** 2 + 5)), li=li, + save_plot=save_plot, save_plot_kwargs=save_plot_kwargs) + + if ret_df: + return df
+ +
[docs] def duplicates(self, top_Rep=20, inform=None): + """Performs a duplicates test and maps the duplicates count in descending + order. + + Args: + verbose: tells how many duplicated entries were found and prints the + top numbers according to the top_Rep argument. Defaluts to True. + top_Rep: int or None. Chooses how many duplicated entries will be + shown withe the top repititions. Defaluts to 20. If None, returns + al the ordered repetitions. + + Returns: + DataFrame with the duplicated records and their occurrence counts, + in descending order (if verbose is False; if True, prints to + terminal). + + Raises: + ValueError: if the `top_Rep` arg is not int or None. + """ + if top_Rep is not None and not isinstance(top_Rep, int): + raise ValueError('The top_Rep argument must be an int or None.') + + dup = self[['seq']][self.seq.duplicated(keep=False)] + dup_count = dup.groupby(self.seq).count() + + dup_count.index.names = ['Entries'] + dup_count.rename(columns={'seq': 'Count'}, inplace=True) + + dup_count.sort_values('Count', ascending=False, inplace=True) + + # self.maps['dup'] = dup_count.index[:top_Rep].values # array + + if self.verbose: + print(f'\nFound {len(dup_count)} duplicated entries.\n' + f'The entries with the {top_Rep} highest repitition counts are:') + print(dup_count.head(top_Rep)) + else: + return dup_count
+ + +
[docs]class Mantissas(object): + """ + Returns a Series with the data mantissas, + + Args: + data: sequence to compute mantissas from, numpy 1D array, pandas + Series of pandas DataFrame column. + Attributes: + data (DataFrame): holds the computed mantissas and, if the arc_test + is also called, the respecttive x and Y coordinates for the plot. + stats (dict): holds the relevant statistics about the data mantissas. + """ + + def __init__(self, data): + + data = Series(_check_num_array_(data)) + data = data.dropna().loc[data != 0].abs() + + self.data = DataFrame({'Mantissa': get_mantissas(data.abs())}) + + self.stats = {'Mean': self.data.Mantissa.mean(), + 'Var': self.data.Mantissa.var(), + 'Skew': self.data.Mantissa.skew(), + 'Kurt': self.data.Mantissa.kurt()} + +
[docs] def report(self, show_plot=True, save_plot=None, save_plot_kwargs=None): + """Displays the Mantissas stats. + + Args: + show_plot: shows the ordered mantissas plot and the Arc Test plot. + Defaults to True. + save_plot: string with the path/name of the file in which the generated + plot will be saved. Uses matplotlib.pyplot.savefig(). File format + is infered by the file name extension. Only available when + plot=True. + save_plot_kwargs: dict with any of the kwargs accepted by + matplotlib.pyplot.savefig() + https://matplotlib.org/api/_as_gen/matplotlib.pyplot.savefig.html + Only available when plot=True and save_plot is a string with the + figure file path/name. + """ + print("\n", ' Mantissas Test '.center(52, '#')) + print(f"\nThe Mantissas MEAN is {self.stats['Mean']:.6f}." + "\tRef: 0.5") + print(f"The Mantissas VARIANCE is {self.stats['Var']:.6f}." + "\tRef: 0.08333") + print(f"The Mantissas SKEWNESS is {self.stats['Skew']:.6f}." + "\tRef: 0.0") + print(f"The Mantissas KURTOSIS is {self.stats['Kurt']:.6f}." + "\tRef: -1.2\n") + if show_plot: + self.show_plot(save_plot=save_plot, save_plot_kwargs=save_plot_kwargs) + self.arc_test(save_plot=save_plot, save_plot_kwargs=save_plot_kwargs)
+ +
[docs] def show_plot(self, figsize=(12, 12), save_plot=None, save_plot_kwargs=None): + """Plots the ordered mantissas and compares them to the expected, straight + line that should be formed in a Benford-cmpliant set. + + Args: + figsize: tuple that sets the figure size. + save_plot: string with the path/name of the file in which the generated + plot will be saved. Uses matplotlib.pyplot.savefig(). File format + is infered by the file name extension. + save_plot_kwargs: dict with any of the kwargs accepted by + matplotlib.pyplot.savefig() + https://matplotlib.org/api/_as_gen/matplotlib.pyplot.savefig.html + Only available when save_plot is a string with the figure file + path/name. + """ + plot_ordered_mantissas(self.data.Mantissa, figsize=figsize, + save_plot=save_plot, save_plot_kwargs=save_plot_kwargs)
+ +
[docs] def arc_test(self, grid=True, figsize=12, save_plot=None, save_plot_kwargs=None): + """ + Add two columns to Mantissas's DataFrame equal to their "X" and "Y" + coordinates, plots its to a scatter plot and calculates the gravity + center of the circle. + + Args: + grid:show grid of the plot. Defaluts to True. + figsize: size of the figure to be displayed. Since it is a square, + there is no need to provide a tuple, like is usually the case with + matplotlib. + save_plot: string with the path/name of the file in which the generated + plot will be saved. Uses matplotlib.pyplot.savefig(). File format + is infered by the file name extension. + save_plot_kwargs: dict with any of the kwargs accepted by + matplotlib.pyplot.savefig() + https://matplotlib.org/api/_as_gen/matplotlib.pyplot.savefig.html + Only available when save_plot is a string with the figure file + path/name. + """ + if self.stats.get('gravity_center') is None: + self.data['mant_x'] = cos(2 * pi * self.data.Mantissa) + self.data['mant_y'] = sin(2 * pi * self.data.Mantissa) + self.stats['gravity_center'] = (self.data.mant_x.mean(), + self.data.mant_y.mean()) + plot_mantissa_arc_test(self.data, self.stats['gravity_center'], + figsize=figsize, save_plot=save_plot, + save_plot_kwargs=save_plot_kwargs)
+ + +
[docs]class Roll_mad(object): + """Applies the MAD to sequential subsets of the Series, returning another + Series. + + Args: + data: sequence of numbers to be evaluated. Must be a numpy 1D array, + a pandas Series or a pandas DataFrame column, with values being + integers or floats. + test: tells which test to use. 1: Fisrt Digits; 2: First Two Digits; + 3: First Three Digits; 22: Second Digit; and -2: Last Two Digits. + window: size of the subset to be used. + decimals: number of decimal places to consider. Defaluts to 2. + If integers, set to 0. If set to -infer-, it will remove the zeros + and consider up to the fifth decimal place to the right, but will + loose performance. + sign: tells which portion of the data to consider. pos: only the positive + entries; neg: only negative entries; all: all entries but zeros. + Defaults to all. + + """ + + def __init__(self, data, test, window, decimals=2, sign='all'): + + #: the test (F1D, SD, F2D...) used for the MAD calculation and critical values + self.test = _check_test_(test) + + if not isinstance(data, Source): + data = Source(data, sign=sign, decimals=decimals, verbose=False) + + Exp, ind = prep_to_roll(data, self.test) + + self.roll_series = data[digs_dict[test]].rolling( + window=window).apply(mad_to_roll, + args=(Exp, ind), raw=False) + self.roll_series.dropna(inplace=True) + +
[docs] def show_plot(self, figsize=(15, 8), save_plot=None, save_plot_kwargs=None): + """Shows the rolling MAD plot + + Args: + figsize: the figure dimensions. + save_plot: string with the path/name of the file in which the generated + plot will be saved. Uses matplotlib.pyplot.savefig(). File format + is infered by the file name extension. + save_plot_kwargs: dict with any of the kwargs accepted by + matplotlib.pyplot.savefig() + https://matplotlib.org/api/_as_gen/matplotlib.pyplot.savefig.html + Only available when save_plot is a string with the figure file + path/name. + """ + plot_roll_mad(self, figsize=figsize, + save_plot=save_plot, save_plot_kwargs=save_plot_kwargs)
+ + +
[docs]class Roll_mse(object): + """Applies the MSE to sequential subsets of the Series, returning another + Series. + + Args: + data: sequence of numbers to be evaluated. Must be a numpy 1D array, + a pandas Series or a pandas DataFrame column, with values being + integers or floats. + test: tells which test to use. 1: Fisrt Digits; 2: First Two Digits; + 3: First Three Digits; 22: Second Digit; and -2: Last Two Digits. + window: size of the subset to be used. + decimals: number of decimal places to consider. Defaluts to 2. + If integers, set to 0. If set to -infer-, it will remove the zeros + and consider up to the fifth decimal place to the right, but will + loose performance. + sign: tells which portion of the data to consider. 'pos': only the positive + entries; 'neg': only negative entries; 'all': all entries but zeros. + Defaults to 'all'. + """ + + def __init__(self, data, test, window, decimals=2, sign='all'): + + test = _check_test_(test) + + if not isinstance(data, Source): + data = Source(data, sign=sign, decimals=decimals, verbose=False) + + Exp, ind = prep_to_roll(data, test) + + self.roll_series = data[digs_dict[test]].rolling( + window=window).apply(mse_to_roll, + args=(Exp, ind), raw=False) + self.roll_series.dropna(inplace=True) + +
[docs] def show_plot(self, figsize=(15, 8), save_plot=None, save_plot_kwargs=None): + """Shows the rolling MSE plot + + Args: + figsize: the figure dimensions. + save_plot: string with the path/name of the file in which the generated + plot will be saved. Uses matplotlib.pyplot.savefig(). File format + is infered by the file name extension. + save_plot_kwargs: dict with any of the kwargs accepted by + matplotlib.pyplot.savefig() + https://matplotlib.org/api/_as_gen/matplotlib.pyplot.savefig.html + Only available when save_plot is a string with the figure file + path/name. + """ + plot_roll_mse(self.roll_series, figsize=figsize, + save_plot=save_plot, save_plot_kwargs=save_plot_kwargs)
+ + +
[docs]def first_digits(data, digs, decimals=2, sign='all', verbose=True, + confidence=None, high_Z='pos', limit_N=None, + MAD=False, MSE=False, chi_square=False, KS=False, + show_plot=True, save_plot=None, save_plot_kwargs=None, + inform=None): + """Performs the Benford First Digits test on the series of + numbers provided. + + Args: + data: sequence of numbers to be evaluated. Must be a numpy 1D array, + a pandas Series or a pandas DataFrame column, with values being + integers or floats. + decimals: number of decimal places to consider. Defaluts to 2. + If integers, set to 0. If set to -infer-, it will remove the zeros + and consider up to the fifth decimal place to the right, but will + loose performance. + sign: tells which portion of the data to consider. 'pos': only the positive + entries; 'neg': only negative entries; 'all': all entries but zeros. + Defaults to 'all'. + digs: number of first digits to consider. Must be 1 (first digit), + 2 (first two digits) or 3 (first three digits). + verbose: tells the number of registries that are being subjected to + the analysis and returns tha analysis DataFrame sorted by the + highest Z score down. Defaults to True. + MAD: calculates the Mean Absolute Difference between the + found and the expected distributions; defaults to False. + confidence: confidence level to draw lower and upper limits when + plotting and to limit the top deviations to show. Defaults to None. + high_Z: chooses which Z scores to be used when displaying results, + according to the confidence level chosen. Defaluts to 'pos', + which will highlight only values higher than the expexted + frequencies; 'all' will highlight both extremes (positive and + negative); and an integer, which will use the first n entries, + positive and negative, regardless of whether Z is higher than + the confidence or not. + limit_N: sets a limit to N as the sample size for the calculation of + the Z scores if the sample is too big. Defaults to None. + MSE: calculates the Mean Square Error of the sample; defaults to + False. + chi_square: calculates the chi_square statistic of the sample and + compares it with a critical value, according to the confidence + level chosen and the series's degrees of freedom. Defaults to + False. Requires confidence != None. + KS: calculates the Kolmogorov-Smirnov test, comparing the cumulative + distribution of the sample with the Benford's, according to the + confidence level chosen. Defaults to False. Requires confidence + != None. + show_plot: draws the test plot. + save_plot: string with the path/name of the file in which the generated + plot will be saved. Uses matplotlib.pyplot.savefig(). File format + is infered by the file name extension. Only available when + plot=True. + save_plot_kwargs: dict with any of the kwargs accepted by + matplotlib.pyplot.savefig() + https://matplotlib.org/api/_as_gen/matplotlib.pyplot.savefig.html + Only available when plot=True and save_plot is a string with the + figure file path/name. + + Returns: + DataFrame with the Expected and Found proportions, and the Z scores of + the differences if the confidence is not None. + """ + verbose = _deprecate_inform_(verbose, inform) + + if not isinstance(data, Source): + data = Source(data, decimals=decimals, sign=sign, verbose=verbose) + + data = data.first_digits(digs, confidence=confidence, high_Z=high_Z, + limit_N=limit_N, MAD=MAD, MSE=MSE, + chi_square=chi_square, KS=KS, show_plot=show_plot, + save_plot=save_plot, save_plot_kwargs=save_plot_kwargs, + ret_df=True) + + if confidence is not None: + data = data[['Counts', 'Found', 'Expected', 'Z_score']] + return data.sort_values('Z_score', ascending=False) + else: + return data[['Counts', 'Found', 'Expected']]
+ + +
[docs]def second_digit(data, decimals=2, sign='all', verbose=True, + confidence=None, high_Z='pos', limit_N=None, + MAD=False, MSE=False, chi_square=False, KS=False, + show_plot=True, save_plot=None, save_plot_kwargs=None, + inform=None): + """Performs the Benford Second Digits test on the series of + numbers provided. + + Args: + data: sequence of numbers to be evaluated. Must be a numpy 1D array, + a pandas Series or a pandas DataFrame column, with values being + integers or floats. + decimals: number of decimal places to consider. Defaluts to 2. + If integers, set to 0. If set to -infer-, it will remove the zeros + and consider up to the fifth decimal place to the right, but will + loose performance. + sign: tells which portion of the data to consider. 'pos': only the positive + entries; 'neg': only negative entries; 'all': all entries but zeros. + Defaults to 'all'. + verbose: tells the number of registries that are being subjected to + the analysis and returns tha analysis DataFrame sorted by the + highest Z score down. Defaults to True. + MAD: calculates the Mean Absolute Difference between the + found and the expected distributions; defaults to False. + confidence: confidence level to draw lower and upper limits when + plotting and to limit the top deviations to show. Defaults to None. + high_Z: chooses which Z scores to be used when displaying results, + according to the confidence level chosen. Defaluts to 'pos', + which will highlight only values higher than the expexted + frequencies; 'all' will highlight both extremes (positive and + negative); and an integer, which will use the first n entries, + positive and negative, regardless of whether Z is higher than + the confidence or not. + limit_N: sets a limit to N as the sample size for the calculation of + the Z scores if the sample is too big. Defaults to None. + MSE: calculates the Mean Square Error of the sample; defaults to + False. + chi_square: calculates the chi_square statistic of the sample and + compares it with a critical value, according to the confidence + level chosen and the series's degrees of freedom. Defaults to + False. Requires confidence != None. + KS: calculates the Kolmogorov-Smirnov test, comparing the cumulative + distribution of the sample with the Benford's, according to the + confidence level chosen. Defaults to False. Requires confidence + != None. + show_plot: draws the test plot. + save_plot: string with the path/name of the file in which the generated + plot will be saved. Uses matplotlib.pyplot.savefig(). File format + is infered by the file name extension. Only available when + plot=True. + save_plot_kwargs: dict with any of the kwargs accepted by + matplotlib.pyplot.savefig() + https://matplotlib.org/api/_as_gen/matplotlib.pyplot.savefig.html + Only available when plot=True and save_plot is a string with the + figure file path/name. + + Returns: + DataFrame with the Expected and Found proportions, and the Z scores of + the differences if the confidence is not None. + """ + verbose = _deprecate_inform_(verbose, inform) + + if not isinstance(data, Source): + data = Source(data, sign=sign, decimals=decimals, verbose=verbose) + + data = data.second_digit(confidence=confidence, high_Z=high_Z, + limit_N=limit_N, MAD=MAD, MSE=MSE, + chi_square=chi_square, KS=KS, show_plot=show_plot, + save_plot=save_plot, save_plot_kwargs=save_plot_kwargs, + ret_df=True) + if confidence is not None: + data = data[['Counts', 'Found', 'Expected', 'Z_score']] + return data.sort_values('Z_score', ascending=False) + else: + return data[['Counts', 'Found', 'Expected']]
+ + +
[docs]def last_two_digits(data, decimals=2, sign='all', verbose=True, + confidence=None, high_Z='pos', limit_N=None, + MAD=False, MSE=False, chi_square=False, KS=False, + show_plot=True, save_plot=None, save_plot_kwargs=None, + inform=None): + """Performs the Last Two Digits test on the series of + numbers provided. + + Args: + data: sequence of numbers to be evaluated. Must be a numpy 1D array, + a pandas Series or a pandas DataFrame column,with values being + integers or floats. + decimals: number of decimal places to consider. Defaluts to 2. + If integers, set to 0. If set to -infer-, it will remove the zeros + and consider up to the fifth decimal place to the right, but will + loose performance. + sign: tells which portion of the data to consider. 'pos': only the positive + entries; 'neg': only negative entries; 'all': all entries but zeros. + Defaults to 'all'. + verbose: tells the number of registries that are being subjected to + the analysis and returns tha analysis DataFrame sorted by the + highest Z score down. Defaults to True. + confidence: confidence level to draw lower and upper limits when + plotting and to limit the top deviations to show. Defaults to None. + high_Z: chooses which Z scores to be used when displaying results, + according to the confidence level chosen. Defaluts to 'pos', + which will highlight only values higher than the expexted + frequencies; 'all' will highlight both extremes (positive and + negative); and an integer, which will use the first n entries, + positive and negative, regardless of whether Z is higher than + the confidence or not. + limit_N: sets a limit to N as the sample size for the calculation of + the Z scores if the sample is too big. Defaults to None. + MAD: calculates the Mean Absolute Difference between the + found and the expected distributions; defaults to False. + MSE: calculates the Mean Square Error of the sample; defaults to + False. + chi_square: calculates the chi_square statistic of the sample and + compares it with a critical value, according to the confidence + level chosen and the series's degrees of freedom. Defaults to + False. Requires confidence != None. + KS: calculates the Kolmogorov-Smirnov test, comparing the cumulative + distribution of the sample with the Benford's, according to the + confidence level chosen. Defaults to False. Requires confidence + != None. + show_plot: draws the test plot. + save_plot: string with the path/name of the file in which the generated + plot will be saved. Uses matplotlib.pyplot.savefig(). File format + is infered by the file name extension. Only available when + plot=True. + save_plot_kwargs: dict with any of the kwargs accepted by + matplotlib.pyplot.savefig() + https://matplotlib.org/api/_as_gen/matplotlib.pyplot.savefig.html + Only available when plot=True and save_plot is a string with the + figure file path/name. + + Returns: + DataFrame with the Expected and Found proportions, and the Z scores of + the differences if the confidence is not None. + """ + verbose = _deprecate_inform_(verbose, inform) + + if not isinstance(data, Source): + data = Source(data, decimals=decimals, sign=sign, verbose=verbose) + + data = data.last_two_digits(confidence=confidence, high_Z=high_Z, + limit_N=limit_N, MAD=MAD, + MSE=MSE, chi_square=chi_square, KS=KS, + show_plot=show_plot, save_plot=save_plot, + save_plot_kwargs=save_plot_kwargs, ret_df=True) + + if confidence is not None: + data = data[['Counts', 'Found', 'Expected', 'Z_score']] + return data.sort_values('Z_score', ascending=False) + else: + return data[['Counts', 'Found', 'Expected']]
+ + +
[docs]def mantissas(data, report=True, show_plot=True, arc_test=True, + save_plot=None, save_plot_kwargs=None, inform=None): + """Extraxts the mantissas of the records logarithms + + Args: + data: sequence to compute mantissas from, numpy 1D array, pandas Series + of pandas DataFrame column. + report: prints the mamtissas mean, variance, skewness and kurtosis + for the sequence studied, along with reference values. + show_plot: plots the ordered mantissas and a line with the expected + inclination. Defaults to True. + arc_test: draws the Arc Test plot. Defaluts to True. + save_plot: string with the path/name of the file in which the generated + plot will be saved. Uses matplotlib.pyplot.savefig(). File format + is infered by the file name extension. Only available when + plot=True. + save_plot_kwargs: dict with any of the kwargs accepted by + matplotlib.pyplot.savefig() + https://matplotlib.org/api/_as_gen/matplotlib.pyplot.savefig.html + Only available when plot=True and save_plot is a string with the + figure file path/name. + + Returns: + Series with the data mantissas. + """ + report = _deprecate_inform_(report, inform) + + mant = Mantissas(data) + if report: + mant.report(save_plot=save_plot, save_plot_kwargs=save_plot_kwargs) + if show_plot: + mant.show_plot(save_plot=save_plot, save_plot_kwargs=save_plot_kwargs) + if arc_test: + mant.arc_test(save_plot=save_plot, save_plot_kwargs=save_plot_kwargs) + return mant
+ + +
[docs]def summation(data, digs=2, decimals=2, sign='all', top=20, verbose=True, + show_plot=True, save_plot=None, save_plot_kwargs=None, inform=None): + """Performs the Summation test. In a Benford series, the sums of the + entries begining with the same digits tends to be the same. + Works only with the First Digits (1, 2 or 3) test. + + Args: + digs: tells the first digits to use: 1- first; 2- first two; + 3- first three. Defaults to 2. + decimals: number of decimal places to consider. Defaluts to 2. + If integers, set to 0. If set to -infer-, it will remove the zeros + and consider up to the fifth decimal place to the right, but will + loose performance. + top: choses how many top values to show. Defaults to 20. + show_plot: plots the results. Defaults to True. + save_plot: string with the path/name of the file in which the generated + plot will be saved. Uses matplotlib.pyplot.savefig(). File format + is infered by the file name extension. Only available when + plot=True. + save_plot_kwargs: dict with any of the kwargs accepted by + matplotlib.pyplot.savefig() + https://matplotlib.org/api/_as_gen/matplotlib.pyplot.savefig.html + Only available when plot=True and save_plot is a string with the + figure file path/name. + + Returns: + DataFrame with the Summation test, whether sorted in descending order + (if verbose == True) or not. + """ + verbose = _deprecate_inform_(verbose, inform) + + if not isinstance(data, Source): + data = Source(data, sign=sign, decimals=decimals, verbose=verbose) + + data = data.summation(digs=digs, top=top, verbose=verbose, + show_plot=show_plot, save_plot=save_plot, + save_plot_kwargs=save_plot_kwargs, ret_df=True) + if verbose: + return data.sort_values('AbsDif', ascending=False) + else: + return data
+ + +
[docs]def mad(data, test, decimals=2, sign='all', verbose=False): + """Calculates the Mean Absolute Deviation of the Series + + Args: + data: sequence of numbers to be evaluated. Must be a numpy 1D array, + a pandas Series or a pandas DataFrame column, with values being + integers or floats. + test: informs which base test to use for the mad. + decimals: number of decimal places to consider. Defaluts to 2. + If integers, set to 0. If set to -infer-, it will remove the zeros + and consider up to the fifth decimal place to the right, but will + loose performance. + sign: tells which portion of the data to consider. pos: only the positive + entries; neg: only negative entries; all: all entries but zeros. + Defaults to all. + Returns: + float: the Mean Absolute Deviation of the Series + """ + data = _check_num_array_(data) + test = _check_test_(test) + start = Source(data, sign=sign, decimals=decimals, verbose=verbose) + if test in [1, 2, 3]: + start.first_digits(digs=test, MAD=True, MSE=True, simple=True) + elif test == 22: + start.second_digit(MAD=True, MSE=False, simple=True) + else: + start.last_two_digits(MAD=True, MSE=False, simple=True) + return start.MAD
+ + +
[docs]def mse(data, test, decimals=2, sign='all', verbose=False): + """Calculates the Mean Squared Error of the Series + + Args: + data: sequence of numbers to be evaluated. Must be a numpy 1D array, + a pandas Series or a pandas DataFrame column, with values being + integers or floats. + test: informs which base test to use for the mad. + decimals: number of decimal places to consider. Defaluts to 2. + If integers, set to 0. If set to -infer-, it will remove the zeros + and consider up to the fifth decimal place to the right, but will + loose performance. + sign: tells which portion of the data to consider. pos: only the positive + entries; neg: only negative entries; all: all entries but zeros. + Defaults to all. + Returns: + float: the Mean Squared Error of the Series + """ + data = _check_num_array_(data) + test = _check_test_(test) + start = Source(data, sign=sign, decimals=decimals, verbose=verbose) + if test in [1, 2, 3]: + start.first_digits(digs=test, MAD=False, MSE=True, simple=True) + elif test == 22: + start.second_digit(MAD=False, MSE=True, simple=True) + else: + start.last_two_digits(MAD=False, MSE=True, simple=True) + return start.MSE
+ + +
[docs]def mad_summ(data, test, decimals=2, sign='all', verbose=False): + """Calculate the Mean Absolute Deviation of the Summation Test + + Args: + data: sequence of numbers to be evaluated. Must be a numpy 1D array, + a pandas Series or a pandas DataFrame column, with values being + integers or floats. + test: informs which base test to use for the summation mad. + decimals: number of decimal places to consider. Defaluts to 2. + If integers, set to 0. If set to -infer-, it will remove the zeros + and consider up to the fifth decimal place to the right, but will + loose performance. + sign: tells which portion of the data to consider. pos: only the positive + entries; neg: only negative entries; all: all entries but zeros. + Defaults to all. + + Returns: + float: the Mean Absolute Deviation of the Summation Test + """ + data = _check_num_array_(data) + test = _check_digs_(test) + + start = Source(data, sign=sign, decimals=decimals, verbose=verbose) + temp = start.loc[start.ZN >= 10 ** (test - 1)] + temp[digs_dict[test]] = (temp.ZN // 10 ** ((log10(temp.ZN).astype( + int)) - (test - 1))).astype( + int) + li = 1. / (9 * (10 ** (test - 1))) + + df = temp.groupby(digs_dict[test]).sum() + return mean(abs(df.ZN / df.ZN.sum() - li))
+ + +
[docs]def rolling_mad(data, test, window, decimals=2, sign='all', + show_plot=False, save_plot=None, save_plot_kwargs=None): + """Applies the MAD to sequential subsets of the records. + + Args: + data: sequence of numbers to be evaluated. Must be a numpy 1D array, + a pandas Series or a pandas DataFrame column, with values being + integers or floats. + test: tells which test to use. 1: Fisrt Digits; 2: First Two Digits; + 3: First Three Digits; 22: Second Digit; and -2: Last Two Digits. + window: size of the subset to be used. + decimals: number of decimal places to consider. Defaluts to 2. + If integers, set to 0. If set to -infer-, it will remove the zeros + and consider up to the fifth decimal place to the right, but will + loose performance. + sign: tells which portion of the data to consider. pos: only the positive + entries; neg: only negative entries; all: all entries but zeros. + Defaults to all. + show_plot: draws the test plot. + save_plot: string with the path/name of the file in which the generated + plot will be saved. Uses matplotlib.pyplot.savefig(). File format + is infered by the file name extension. Only available when + plot=True. + save_plot_kwargs: dict with any of the kwargs accepted by + matplotlib.pyplot.savefig() + https://matplotlib.org/api/_as_gen/matplotlib.pyplot.savefig.html + Only available when plot=True and save_plot is a string with the + figure file path/name. + + Returns: + Series with sequentially computed MADs. + """ + data = _check_num_array_(data) + r_mad = Roll_mad(data, test, window, decimals, sign) + if show_plot: + r_mad.show_plot(save_plot=save_plot, save_plot_kwargs=save_plot_kwargs) + return r_mad.roll_series
+ + +
[docs]def rolling_mse(data, test, window, decimals=2, sign='all', + show_plot=False, save_plot=None, save_plot_kwargs=None): + """Applies the MSE to sequential subsets of the records. + + Args: + data: sequence of numbers to be evaluated. Must be a numpy 1D array, + a pandas Series or a pandas DataFrame column, with values being + integers or floats. + test: tells which test to use. 1: Fisrt Digits; 2: First Two Digits; + 3: First Three Digits; 22: Second Digit; and -2: Last Two Digits. + window: size of the subset to be used. + decimals: number of decimal places to consider. Defaluts to 2. + If integers, set to 0. If set to -infer-, it will remove the zeros + and consider up to the fifth decimal place to the right, but will + loose performance. + sign: tells which portion of the data to consider. pos: only the positive + entries; neg: only negative entries; all: all entries but zeros. + Defaults to all. + show_plot: draws the test plot. + save_plot: string with the path/name of the file in which the generated + plot will be saved. Uses matplotlib.pyplot.savefig(). File format + is infered by the file name extension. Only available when + plot=True. + save_plot_kwargs: dict with any of the kwargs accepted by + matplotlib.pyplot.savefig() + https://matplotlib.org/api/_as_gen/matplotlib.pyplot.savefig.html + Only available when plot=True and save_plot is a string with the + figure file path/name. + + Returns: + Series with sequentially computed MSEs. + """ + data = _check_num_array_(data) + r_mse = Roll_mse(data, test, window, decimals, sign) + if show_plot: + r_mse.show_plot(save_plot=save_plot, save_plot_kwargs=save_plot_kwargs) + return r_mse.roll_series
+ + +
[docs]def duplicates(data, top_Rep=20, verbose=True, inform=None): + """Performs a duplicates test and maps the duplicates count in descending + order. + + Args: + data: sequence to take the duplicates from. pandas Series or + numpy Ndarray. + verbose: tells how many duplicated entries were found and prints the + top numbers according to the top_Rep argument. Defaluts to True. + top_Rep: chooses how many duplicated entries will be + shown withe the top repititions. int or None. Defaluts to 20. + If None, returns al the ordered repetitions. + + Returns: + DataFrame with the duplicated records and their respective counts + + Raises: + ValueError: if the `top_Rep` arg is not int or None. + """ + verbose = _deprecate_inform_(verbose, inform) + + if top_Rep is not None and not isinstance(top_Rep, int): + raise ValueError('The top_Rep argument must be an int or None.') + + if not isinstance(data, Series): + try: + data = Series(data) + except ValueError: + print('\ndata must be a numpy Ndarray or a pandas Series.') + + dup = data.loc[data.duplicated(keep=False)] + dup_count = dup.value_counts() + + dup_count.index.names = ['Entries'] + dup_count.name = 'Count' + + if verbose: + print(f'\nFound {len(dup_count)} duplicated entries.\n' + f'The entries with the {top_Rep} highest repitition counts are:') + print(dup_count.head(top_Rep)) + + return dup_count
+ + +
[docs]def second_order(data, test, decimals=2, sign='all', verbose=True, MAD=False, + confidence=None, high_Z='pos', limit_N=None, MSE=False, + show_plot=True, save_plot=None, save_plot_kwargs=None, inform=None): + """Performs the chosen test after subtracting the ordered sequence by itself. + Hence Second Order. + + Args: + data: sequence of numbers to be evaluated. Must be a numpy 1D array, + a pandas Series or a pandas DataFrame column, with values being + integers or floats. + test: the test to be performed - 1 or 'F1D': First Digit; 2 or 'F2D': + First Two Digits; 3 or 'F3D': First three Digits; 22 or 'SD': + Second Digits; -2 or 'L2D': Last Two Digits. + decimals: number of decimal places to consider. Defaluts to 2. + If integers, set to 0. If set to -infer-, it will remove the zeros + and consider up to the fifth decimal place to the right, but will + loose performance. + sign: tells which portion of the data to consider. pos: only the positive + entries; neg: only negative entries; all: all entries but zeros. + Defaults to all. + verbose: tells the number of registries that are being subjected to + the analysis and returns tha analysis DataFrame sorted by the + highest Z score down. Defaults to True. + MAD: calculates the Mean Absolute Difference between the + found and the expected distributions; defaults to False. + confidence: confidence level to draw lower and upper limits when + plotting and to limit the top deviations to show. Defaults to None. + high_Z: chooses which Z scores to be used when displaying results, + according to the confidence level chosen. Defaluts to 'pos', + which will highlight only values higher than the expexted + frequencies; 'all' will highlight both extremes (positive and + negative); and an integer, which will use the first n entries, + positive and negative, regardless of whether Z is higher than + the confidence or not. + limit_N: sets a limit to N as the sample size for the calculation of + the Z scores if the sample is too big. Defaults to None. + MSE: calculates the Mean Square Error of the sample; defaults to + False. + chi_square: calculates the chi_square statistic of the sample and + compares it with a critical value, according to the confidence + level chosen and the series's degrees of freedom. Defaults to + False. Requires confidence != None. + KS: calculates the Kolmogorov-Smirnov test, comparing the cumulative + distribution of the sample with the Benford's, according to the + confidence level chosen. Defaults to False. Requires confidence + != None. + show_plot: draws the test plot. + save_plot: string with the path/name of the file in which the generated + plot will be saved. Uses matplotlib.pyplot.savefig(). File format + is infered by the file name extension. Only available when + plot=True. + save_plot_kwargs: dict with any of the kwargs accepted by + matplotlib.pyplot.savefig() + https://matplotlib.org/api/_as_gen/matplotlib.pyplot.savefig.html + Only available when plot=True and save_plot is a string with the + figure file path/name. + + Returns: + DataFrame of the test chosen, but applied on Second Order pre- + processed data. + """ + test = _check_test_(test) + + verbose = _deprecate_inform_(verbose, inform) + + data = Source(data, decimals=decimals, sign=sign, + sec_order=True, verbose=verbose) + if test in [1, 2, 3]: + data.first_digits(digs=test, MAD=MAD, + confidence=confidence, high_Z=high_Z, + limit_N=limit_N, MSE=MSE, show_plot=show_plot, + save_plot=save_plot, save_plot_kwargs=save_plot_kwargs) + elif test == 22: + data.second_digit(MAD=MAD, confidence=confidence, high_Z=high_Z, + limit_N=limit_N, MSE=MSE, show_plot=show_plot, + save_plot=save_plot, save_plot_kwargs=save_plot_kwargs) + else: + data.last_two_digits(MAD=MAD, confidence=confidence, high_Z=high_Z, + limit_N=limit_N, MSE=MSE, show_plot=show_plot, + save_plot=save_plot, save_plot_kwargs=save_plot_kwargs) + return data
+
+ +
+ +
+ + +
+
+ +
+ +