*Note: The city of Chicago buys goods and services from the local vendors. The actual purchases are made by the various Departments within the city government.

1. Objectives of This Analysis

  • First, I decided to look at the data from the point of view of an investigator looking for corrupt purchasing behavior. The results from this analysis are in Section 3.

  • In Section 3.1, I looked at the data from a political point of view, where I analyzed total payment amounts for all years, departments and vendors and ckecked if there is any pattern for changes since Rahm Emanuel took office in 2011.

  • My purpose in Section 3.2 was finding vendors that received the highest amount of US dollars from departments.
  • Section 3.3 includes analysis for vendors that were receiving payments through contract or direct vendors (no contract or paid by cash).
  • Because payments from 1996 through 2002 have been rolled-up and appear as "2002" in the data, I devided the data set into two data sets and investigated them separately as before 2002, and after 2002. Then, I drilled it down to analyze the total dollars paid by department for every vendor for the years:
    1) 1996-2002
    2) 2002-2018

2. Reading In and Checking the Quality of the Payments Data

In [1]:
import numpy as np
import pandas as pd
import seaborn as sns
import matplotlib.pyplot as plt
from matplotlib.pyplot import figure
%matplotlib inline
import warnings
warnings.filterwarnings('ignore')
import datetime
from matplotlib.ticker import FuncFormatter
In [2]:
payments = pd.read_csv('Payments.csv', low_memory=False)
In [3]:
payments.head()
Out[3]:
VOUCHER NUMBER AMOUNT CHECK DATE DEPARTMENT NAME CONTRACT NUMBER VENDOR NAME CASHED
0 PV99189986031 50.00 04/06/2018 NaN DV HATCHETT, SYDELL True
1 PVRF182700008 29.14 09/05/2018 NaN DV NOEMI GALVEZ-DELIRA True
2 PVRF182700007 29.14 09/04/2018 NaN DV ANTONIO REED True
3 PVCI19CI500124 137607.50 03/11/2019 DEPT OF BUSINESS & INFORMATION SERVICES 32678 CARMINATI CONSULTING True
4 PV991999612973 43878.68 03/05/2019 NaN DV RUSH SURGICENTER LTD True
In [4]:
payments.info()
<class 'pandas.core.frame.DataFrame'>
RangeIndex: 797256 entries, 0 to 797255
Data columns (total 7 columns):
VOUCHER NUMBER     646912 non-null object
AMOUNT             797256 non-null float64
CHECK DATE         797256 non-null object
DEPARTMENT NAME    123421 non-null object
CONTRACT NUMBER    797256 non-null object
VENDOR NAME        797246 non-null object
CASHED             786199 non-null object
dtypes: float64(1), object(6)
memory usage: 42.6+ MB
In [5]:
payments.shape
Out[5]:
(797256, 7)
In [6]:
ax = sns.heatmap(payments.isnull(),yticklabels=False,cbar=False,cmap='viridis')
ax.set_title("Missing Data Values for the Payments DataSet")
Out[6]:
Text(0.5, 1.0, 'Missing Data Values for the Payments DataSet')

Almost 85% of the department_name column's values is lost.

In [7]:
sum_missing = payments.isnull().sum()
percent_missing = payments.isnull().sum() * 100 / len(payments)

missing_value_df = pd.DataFrame({'column_name': payments.columns,
                                 'total_missing':sum_missing,
                                 'percent_missing': percent_missing})
missing_value_df
Out[7]:
column_name percent_missing total_missing
VOUCHER NUMBER VOUCHER NUMBER 18.857682 150344
AMOUNT AMOUNT 0.000000 0
CHECK DATE CHECK DATE 0.000000 0
DEPARTMENT NAME DEPARTMENT NAME 84.519276 673835
CONTRACT NUMBER CONTRACT NUMBER 0.000000 0
VENDOR NAME VENDOR NAME 0.001254 10
CASHED CASHED 1.386882 11057
In [8]:
payments.drop("VOUCHER NUMBER", axis=1, inplace=True)
In [9]:
payments.rename(columns={'AMOUNT':'amount', 'CHECK DATE':'check_date',
                         'CONTRACT NUMBER':'contract_number',
                         'DEPARTMENT NAME':'department_name',
                         'VENDOR NAME':'vendor_name','CASHED':'cashed'}, 
               inplace=True)
In [10]:
payments["check_date"] = pd.to_datetime(payments["check_date"])

payments['year'] = payments["check_date"].dt.year
payments['month'] = payments["check_date"].dt.month
payments['weekday'] = payments["check_date"].dt.weekday

payments.drop("check_date", axis=1, inplace=True)
In [11]:
payments.head()
Out[11]:
amount department_name contract_number vendor_name cashed year month weekday
0 50.00 NaN DV HATCHETT, SYDELL True 2018 4 4
1 29.14 NaN DV NOEMI GALVEZ-DELIRA True 2018 9 2
2 29.14 NaN DV ANTONIO REED True 2018 9 1
3 137607.50 DEPT OF BUSINESS & INFORMATION SERVICES 32678 CARMINATI CONSULTING True 2019 3 0
4 43878.68 NaN DV RUSH SURGICENTER LTD True 2019 3 1

payments_before_2002 includes 7 years of records. During this time there were 9,526 total payments to all vendors from all departments shown in the records for a total of \$11,120,745,925 which equates to an average payment per transaction of \$1,588,677,989.
payments_after_2002 includes records for 2003-2019. During this time there were 767,730 total payments to all vendors from all departments shown in the records for a total of \$79,800,684,681 which equates to an average payment per transaction of \$4,987,542,793.

In [12]:
payments_before_2002 = payments.loc[payments.year == 2002]
print(payments_before_2002["amount"].sum())
print("\n")
print(payments_before_2002["amount"].sum()/7)
11120745925.45


1588677989.3500001
In [13]:
payments_after_2002 = payments.loc[payments.year > 2002]
print(payments_after_2002["amount"].sum())
print("\n")
print(payments_after_2002["amount"].sum()/16)
79800684681.70004


4987542792.606253

3. Exploring and Visualizing the Payments Data.

3.1 Analyzing data from a political point of view.

  • Looking at the city expenditures before and after 2002 (Fig. 3.1.1).
  • Analyzing how the city expenditures have been evolving since 2003 (Fig. 3.1.2).
  • The total dollars paid by the top 15 departments for all the orders for the years 1996-2018 (Fig. 3.1.3).
  • Investigation of total dollars paid for vendors by Department of Finance for 2003-2018 (Fig. 3.1.4).
  • The total dollars paid by the top 15 departments for small orders only for the years 2003-2018 (Fig. 3.1.5).
  • The total dollars paid by year for the top five departments for all orders for 2003-2018 (Fig. 3.1.6/3.1.6(a)).
  • Top six vendors paid by department of Water Management for the Years 2003-2018 (Fig. 3.1.7).
In [14]:
data = ("payments_before_2002", "payments_after_2002")
y_pos = np.arange(len(data))
amount = [1588677989.3500001, 4987542792.606253]

plt.barh(y_pos, amount, align='center', alpha=0.5)
plt.yticks(y_pos, data)
plt.xlabel('Amount in billions of dollars', fontsize=11)
plt.title("Fig. 3.1.1: The Average Payments Made \n by All Departments for Vendors for Years \n 1996-2002 and 2003-2019", fontsize=13)

def billions(val, pos):
    return f'{val*1e-9:.0f}B'

formatter = FuncFormatter(billions)
plt.gca().xaxis.set_major_formatter(formatter)

Observation: The city expenditures have significantly increased by about 600% from 2003 to 2018.

In [15]:
payments_by_year = payments.groupby(['year']).amount.sum()
payments_by_year.loc[(payments_by_year.index >2002)&(payments_by_year.index != 2019)].plot(kind="line", y="amount", figsize=(8,5))
plt.ylim(bottom=0)

plt.xlabel("Year", fontsize=12)
plt.ylabel("Amount in billions of dollars", fontsize=12)
plt.title("Fig. 3.1.2: The Total Payments Made by \n All Departments for Vendors for 2003-2018", y=1.02, fontsize=14)
def billions(val, pos):
    return f'{val*1e-9:.0f}B'

formatter = FuncFormatter(billions)
plt.gca().yaxis.set_major_formatter(formatter)

Observation: The total amount of payments has been steadly increasing except small declines in 2008 and 2013.

Note: It is likely that the 2007-2008 decline could be attributed at least in part to the financial crisis of 2007–2008. The reasons for the latter decline are less clear but perhaps the 2013 United States debt-ceiling crisis played some role.

  • If departments are buying in amounts of over a \$100.000, it's considered a "Large Order".
  • If departments are buying in amounts of under a \$100,000, it's considered a "Small Order".
In [16]:
grouped_by_dept = payments[payments["department_name"].notnull()]
grouped_by_dept = grouped_by_dept.groupby("department_name").amount.sum().sort_values(ascending=False).reset_index(name='sum').head(15)
grouped_by_dept.plot.barh(x="department_name", y='sum', figsize=(16,10), edgecolor="black")
plt.xlabel("Amount in billions of dollars", fontsize=14)
plt.ylabel("Department", fontsize=14)

plt.title("Fig. 3.1.3: The Total Dollars Paid by the Top 15 \n Departments for All the Orders for the Years 1996-2019", fontsize=18)
def billions(val, pos):
    return f'{val*1e-9:.0f}B'

formatter = FuncFormatter(billions)
plt.gca().xaxis.set_major_formatter(formatter)

Observation: Department of finance has the highest payments followed by the department of aviation for 1996 - 2019.

This plot also represents departments that have the highest payments for large orders.

Note: Consider that we have only 15% available data for the department_name column.

In [17]:
dept_finance = payments.loc[payments.department_name == "DEPARTMENT OF FINANCE"]
In [18]:
top_four = dept_finance.loc[dept_finance.vendor_name.isin(["BLUE CROSS & BLUE SHIELD", "IBM CORPORATION ",
                                  "GLADYS R. WILSON & ASSOCIATES PC", "SEAWAY BANK & TRUST COMPANY",
                                  "A & O RECOVERY SOLUTIONS, LLC."])].groupby(["year",
                                                          "vendor_name"]).amount.sum().unstack()
top_four.fillna(0, inplace=True)
In [19]:
top_four.plot.line(figsize=(18,10))

plt.xlabel("Year", fontsize=14)
plt.ylabel("Amount in millions of dollars", fontsize=14)
plt.title("Figure 3.1.4: The Total Dollars Paid by Year by Department of Finance for the\n Top Four Departments for All Orders for 2003-2018", fontsize=20)

def millions(val, pos):
    return f'{val*1e-6:.0f}M'

formatter = FuncFormatter(millions)
plt.gca().yaxis.set_major_formatter(formatter)

Observation: It's clear that primary purpose of the Department of Finance is to pay Health Insurance.

In [20]:
small_order = payments[(payments["amount"]<100000)&(payments["department_name"].notnull())]
small_order = small_order.groupby("department_name").amount.sum().sort_values(ascending=False).reset_index(name='sum').head(15)
small_order.plot.barh(x="department_name", y='sum', figsize=(16, 10), edgecolor="black")
plt.xlabel("Amount in millions of dollars", fontsize=14)
plt.ylabel("Department", fontsize=14)
plt.title('Fig. 3.1.5: The Total Dollars Paid by Top 15 Departments for Small Orders Only \n for the Years 1996-2019', fontsize=18)
def millions(val, pos):
    return f'{val*1e-6:.0f}M'

formatter = FuncFormatter(millions)
plt.gca().xaxis.set_major_formatter(formatter)

Observation: Department of family and support services has the highest payments for small orders only for 1996 and 2019.

In [21]:
top_five_dept = payments.loc[(payments.department_name.isin(["DEPARTMENT OF FINANCE",
                                                                 "DEPT OF AVIATION",
                                                                 "CHICAGO DEPARTMENT OF TRANSPORTATION",
                                                                 "DEPARTMENT OF WATER MANAGEMENT",
                                                                 "OHARE MODERNIZATION PROJECT"]))&
                             (payments.year != 2002) & (payments.year != 2019)].groupby(["year",
                                                          "department_name"]).amount.sum().unstack()
top_five_dept.fillna(0, inplace=True)
In [22]:
top_five_dept.plot.bar(stacked=True, figsize=(18,10), edgecolor="black")


def billions(val, pos):
    return f'{val*1e-9:.2f}B'

formatter = FuncFormatter(billions)
plt.gca().yaxis.set_major_formatter(formatter)

plt.xlabel("Year", fontsize=14)
plt.ylabel("Amount in billions of dollars", fontsize=14)
plt.title("Figure 3.1.6: The Total Dollars Paid by Year for the Top Five Departments \n for All Orders for 2003-2018", fontsize=20)
cols = top_five_dept.columns.tolist()
cols = ['DEPARTMENT OF FINANCE', 'DEPT OF AVIATION', 'CHICAGO DEPARTMENT OF TRANSPORTATION',
        'DEPARTMENT OF WATER MANAGEMENT', 'OHARE MODERNIZATION PROJECT']
top_five_dept = top_five_dept[cols]

Observation: Department of Finance has the highest expenditures for years 2003-2018.

In [23]:
top_five_dept.plot.line( figsize=(18,10))

plt.xlabel("Year", fontsize=14)
plt.ylabel("Amount in millions of dollars", fontsize=14)
plt.title("Figure 3.1.6(a): The Total Dollars Paid by Year for the Top Five Departments \n for All Orders for 2003-2018", fontsize=20)
cols = top_five_dept.columns.tolist()
cols = ['DEPARTMENT OF FINANCE', 'DEPT OF AVIATION', 'CHICAGO DEPARTMENT OF TRANSPORTATION',
        'DEPARTMENT OF WATER MANAGEMENT', 'OHARE MODERNIZATION PROJECT']
top_five_dept = top_five_dept[cols]

def millions(val, pos):
    return f'{val*1e-6:.0f}M'

formatter = FuncFormatter(millions)
plt.gca().yaxis.set_major_formatter(formatter)

Observation: I looked at the changes after Rahm Emanuel took office in 2011. Amount of payments paid by the Department of Water, Transportation Management and Aviation had increased steadly before deeping slightly after 2016.

In [24]:
water_management_dept = payments_after_2002.loc[(payments_after_2002.department_name ==  "DEPARTMENT OF WATER MANAGEMENT")
                                   & (payments_after_2002.year != 2019)]
In [25]:
vend = water_management_dept[water_management_dept["vendor_name"].notnull()]

vend = vend.groupby("vendor_name").amount.sum().sort_values(ascending=False).reset_index(name='sum').head(6)
vend.plot.barh(x="vendor_name", y='sum', figsize=(16,10), edgecolor="black")
plt.xlabel("Amount in millions of dollars", fontsize=14)
plt.ylabel("Vendors", fontsize=14)
plt.title("Fig 3.1.7: Top Six Vendors Paid by Department of Water Management \n for All Orders for the Years 2003-2018", fontsize=18)
def millions(val, pos):
    return f'{val*1e-6:.0f}M'

formatter = FuncFormatter(millions)
plt.gca().xaxis.set_major_formatter(formatter)

Observation: Kenny Construction Company received the highest amount of payments from the Department of Water Management, almost
$500 millions
.

3.2. Analyzing vendors via vizualization.

  • Top 15 vendors who received the highest amount of payments for all orders for the years 1996-2018 (Fig. 3.2.1).
  • Investigate the vendor that received the highest amount of payments for all orders for the years 1996-2018 and find out the departments that were financing it (Fig. 3.2.2).
  • The Total Dollars Received by year by top four vendors excluding banks for all orders for the years 1996-2018 (Fig. 3.2.3).
In [26]:
grouped_by_vend = payments[payments["vendor_name"].notnull()]
grouped_by_vend = grouped_by_vend.groupby("vendor_name").amount.sum().sort_values(ascending=False).reset_index(name='sum').head(15)
grouped_by_vend.plot.barh(x="vendor_name", y='sum', figsize=(16,10), edgecolor="black")
plt.xlabel("Amount in billions of dollars", fontsize=14)
plt.ylabel("Vendors", fontsize=14)
plt.title("Fig. 3.2.1: The Total Dollars Received by the Top 15 Vendors \n for All Orders for the Years 1996-2018", fontsize=18)
def billions(val, pos):
    return f'{val*1e-9:.0f}B'

formatter = FuncFormatter(billions)
plt.gca().xaxis.set_major_formatter(formatter)
In [27]:
blue_cross = payments.loc[(payments.vendor_name.isin(["BLUE CROSS & BLUE SHIELD"]))&
                                    (payments.year != 2019)]
blue_cross.groupby("year").amount.sum().plot(kind="bar", y="amount", figsize=(14,5), colormap='Paired')

plt.xlabel("Year", fontsize=12)
plt.ylabel("Amount in millions of dollars", fontsize=12)
plt.title('Figure 3.2.2: The Total Dollars Paid by All Departments \n for Blue Cross & Blue Shield for the Years 1996-2018', y=1.02, fontsize=14)
def millions(val, pos):
    return f'{val*1e-6:.0f}M'

formatter = FuncFormatter(millions)
plt.gca().yaxis.set_major_formatter(formatter)

Observation: The highest payments for Blue Cross & Blue Shield was made in 2016.

In [28]:
blue_cross.department_name.value_counts()
Out[28]:
DEPARTMENT OF FINANCE    61
Name: department_name, dtype: int64

Observation: Department of Finance is the only department that has been financing Blue Cross & Blue Shield in the given data set.

Note: Consider that we have only 15% available data for the department_name column.

In [29]:
top_five_vend = payments_after_2002.loc[(payments_after_2002.vendor_name.isin(["KENNY CONSTRUCTION COMPANY",
                                                        "CAREMARK INC",
                                                        "NATIONWIDE RETIREMENT SOLUTION",
                                                        "WALSH CONSTRUCTION COMPANY OF ILL"])) &
                            (payments.year != 2019)]

top_five_vend = top_five_vend.groupby(["year","vendor_name"]).amount.sum().unstack()
top_five_vend.fillna(0, inplace=True)
In [30]:
top_five_vend.plot.line( figsize=(18,10))

def millions(val, pos):
    return f'{val*1e-6:.0f}M'

formatter = FuncFormatter(millions)
plt.gca().yaxis.set_major_formatter(formatter)

plt.xlabel("Year", fontsize=14)
plt.ylabel("Amount in millions of dollars", fontsize=14)
plt.title("Fig. 3.2.3: The Total Dollars Received by Year by Top Four Vendors \n Excluding Banks for All Orders for the Years 2003-2018", fontsize=18)
plt.legend(loc=2)
Out[30]:
<matplotlib.legend.Legend at 0x11128a6a0>

Observation: After a significant raise in 2009, the "Nationwide Retirement Solution" dropped to zero in 2014.

Note: Top five vendors include mostly banks (look at Fig. 3.2.1)

3.3. Analyzing contract and non-contract vendors through vizualization.

  • Investigate vendors who received their payment via contract or/and non-contract.
  • The total dollars paid by departments for contract vendors (Fig. 3.3.1).
  • The total dollars paid by departments for direct vendors or non-contract (Fig. 3.3.2).
  • Analyze departments that were paying without contract.
  • Analyze five top vendors that were receiving non-contract payments (Fig. 3.3.3).
In [31]:
payments.contract_number.value_counts().head()
Out[31]:
DV       579610
33233      5252
19393      1691
28775      1559
24027       836
Name: contract_number, dtype: int64
In [32]:
payments[payments["contract_number"] == 'DV'].shape[0]/len(payments)
Out[32]:
0.7270061310294309

Observation: Almost 73% of the payments has transactioned to DV (Direct Vendor). I called them non-contract vendors.

In [33]:
via_contract = payments_after_2002.loc[(payments_after_2002.contract_number != "DV") &
                                      (payments_after_2002.year !=2019)]
In [34]:
via_contract.head()
Out[34]:
amount department_name contract_number vendor_name cashed year month weekday
14 16653.00 NaN 8627 PROGRESSIVE INDUSTRIES, INC. True 2005 1 5
16 2685.00 NaN 35458 ROGER J BALLA True 2016 1 4
27 9867.48 NaN 19550 KENNY CONSTRUCTION COMPANY True 2018 1 1
28 250000.00 PLANNING & DEVELOPMENT 7585 CHILDSERV 01 True 2010 1 4
77 9374.30 CHICAGO DEPARTMENT OF TRANSPORTATION 44315 AECOM TECHNICAL SERVICES True 2018 9 2
In [35]:
via_contract.groupby("year").amount.sum().plot(kind="bar", colormap="Paired", figsize=(14,5))

plt.xlabel("Year", fontsize=12)
plt.ylabel("Amount in billions of dollars", fontsize=12)
plt.title('Fig. 3.3.1: The Total Dollars Paid by Departments for \n Contract Vendors for the Years 2003-2018', y=1.02, fontsize=14)
def billions(val, pos):
    return f'{val*1e-9:.0f}B'

formatter = FuncFormatter(billions)
plt.gca().yaxis.set_major_formatter(formatter)
In [36]:
via_contract.groupby("vendor_name").amount.sum().sort_values(ascending=False).head(10)
Out[36]:
vendor_name
BLUE CROSS & BLUE SHIELD                        5.124513e+09
CAREMARK INC                                    1.421277e+09
KENNY CONSTRUCTION COMPANY                      1.209277e+09
BENCH MARK CONSTRUCTION. CO. INC                8.886829e+08
F.H. PASCHEN, S.N. NIELSEN & ASSOCIATES, LLC    8.145837e+08
BOARD OF EDUCATION OF THE CITY OF CHICAGO       7.644845e+08
WALSH CONSTRUCTION COMPANY OF ILL               7.183408e+08
PUBLIC BUILDING COMMISSION CHICAGO              7.166066e+08
BIGANE PAVING COMPANY                           6.746772e+08
ALLIED WASTE TRANSPORTATION INC                 6.420991e+08
Name: amount, dtype: float64
In [37]:
no_contract = payments.loc[(payments.contract_number == "DV") & (payments.year != 2019)]
In [38]:
no_contract.groupby("year").amount.sum().plot(kind="bar", colormap="Paired", figsize=(12,5))

plt.xlabel("Year", fontsize=12)
plt.ylabel("Amount in billions of dollars", fontsize=12)
plt.title('Fig. 3.3.2: The Total Dollars Paid by Departments for \n Direct Vendors for the Years 1996-2018', y=1.02, fontsize=14)
def billions(val, pos):
    return f'{val*1e-9:.2f}B'

formatter = FuncFormatter(billions)
plt.gca().yaxis.set_major_formatter(formatter)

Observation: Note that there were no direct payments made before 2010.

I wanted to analyze departments that have been paying without contract, but there wasn't any available data.

In [39]:
ax = sns.heatmap(no_contract.isnull(),yticklabels=False,cbar=False,cmap='viridis')
ax.set_title("Missing Data Values for the No_Contract DataSet")
Out[39]:
Text(0.5, 1.0, 'Missing Data Values for the No_Contract DataSet')

463134 rows are missing.

In [40]:
no_contract.isnull().sum()
Out[40]:
amount                  0
department_name    463134
contract_number         0
vendor_name            10
cashed               7512
year                    0
month                   0
weekday                 0
dtype: int64
In [41]:
no_contract.groupby("vendor_name").amount.sum().sort_values(ascending=False).head(25)
Out[41]:
vendor_name
CITY TREASURER / HARRIS BANK                               4.312634e+09
BANK OF NEW YORK TRUST                                     1.466362e+09
AMALGAMATED BANK OF CHICAGO                                1.255046e+09
BANK OF NY MIDWEST TRUST CO                                1.241188e+09
NATIONWIDE RETIREMENT SOLUTION                             1.005149e+09
CITY OF CHICAGO                                            9.909932e+08
POLICEMENS A & B FUND                                      9.502598e+08
JPMORGAN CHASE BANK NATIONAL ASSOC                         7.788749e+08
U S BANK N A                                               6.941353e+08
MUNICIPAL EMPLOYEE PENSION FD                              6.505316e+08
U.S. BANK01 NATIONAL ASSOCIATION                           4.961950e+08
ZIONS BANK                                                 4.397268e+08
FIREMENS ANNUITY BENEFIT FUND                              4.386452e+08
WELLS FARGO BANK N A                                       4.249120e+08
CITY TREASURER 06                                          4.043749e+08
CHICAGO TRANSITY AUTHORITY.                                3.970521e+08
COOK COUNTY TREASURER                                      3.628423e+08
BANK OF AMERICA IL                                         1.592941e+08
MUNICIPAL EMPLOYEES ANNUITY AND BENEFIT FUND OF CHICAGO    1.417950e+08
MUNICIPAL EMPLOY CREDIT UNION                              1.416110e+08
KENNY CONSTRUCTION COMPANY                                 1.356747e+08
O'HARE MODERNIZATION PROGRAM                               1.240411e+08
CHICAGO PARKING METERS LLC                                 1.214796e+08
DEPARTMENT OF AVIATION                                     1.191837e+08
DEUTSCHE BANK TRUST COMPANY                                1.128100e+08
Name: amount, dtype: float64
In [42]:
top_five = no_contract.loc[(no_contract.vendor_name.isin(["CITY TREASURER / HARRIS BANK", "POLICEMENS A & B FUND",
                         "NATIONWIDE RETIREMENT SOLUTION", "BANK OF NEW YORK TRUST ", "AMALGAMATED BANK OF CHICAGO",
                        "BANK OF NY MIDWEST TRUST CO" ])) & (no_contract.year != 2019)]

top_five = top_five.groupby(["year","vendor_name"]).amount.sum().unstack()
top_five.fillna(0, inplace=True)
In [43]:
top_five.plot(kind="line", figsize=(18,10))


def billions(val, pos):
    return f'{val*1e-9:.2f}B'

formatter = FuncFormatter(billions)
plt.gca().yaxis.set_major_formatter(formatter)

plt.xlabel("Year", fontsize=14)
plt.ylabel("Amount in billions of dollars", fontsize=14)
plt.title(" Fig. 3.3.3: The Total Dollars Paid by Year by Departments for \n Top Five Non-Contract Vendors for the Years 1996-2018", fontsize=18)
plt.legend(loc=2)
Out[43]:
<matplotlib.legend.Legend at 0x110e1c320>

4. Conclusions

It's difficult to draw any firm conclusions regarding the presence or absence of corruption from the given data, but the structure of many of the transactions as well as key missing data would appear to create an environment where corruption could occur:

  1. Top vendors that were receiving the majority of payments are banks (Fig. 3.2.1). The City Treasurer / Harris Bank is leading those banks by receiving almost $4.4 billion in payments between 1996-2018. Presumably, banks are just the conduits for payments to others. If so, the ultimate recipients are not revealed.
  2. There is no data available to show which Department made these payments either.
  3. These are direct payments, which means there is no contract between the city and the vendor. There may be good reasons for this in many cases, but, again, it does create an environment for corruption.

The data doesn't show any record for payments that were made with no contract until 2010. I wasn't able to tell if this means that the city only started to allow direct payments after 2010 or that it has all along but didn't distinguish between direct and contract payments before that.

Overall city expenditures, or at least those recorded in the data increased by about 600% from 2003 to 2018. While there were some variations from the overall trend most notably the dip during the great recession around 2008 and rise in its aftermath), the total annual payments fairly closely followed a straight line with an average annual increase of about $330M per year.

There is no ability to break out the annual payments from 1996-2002, since this data is rolled up as one year. The average annual total payments during this period were just under \$1.6B, which compares to total payments of about \\$1.9B in 2003, the first year the numbers were broken out annually.

I looked at the changes after Rahm Emanuel took office in 2011. Amount of payments paid by the Department of Water, Transportation Management and Aviation had increased steadly before deeping slightly after 2016.

5. Suggestions for Further Analysis

I would like to get answers to questions:

  • Why are the top vendors that have received the highest amount of payments banks?
  • Why doesn't data show us what departments made those payments?
  • If banks are just the conduits for payments to others, the ultimate recipients must be revealed.

Departments play an essential role in this data set because they are the ones that make the city's most expenditures. The city has to do a better job of revealing the ultimate recipients for the Payments data, so citizens can validate the transparency and eliminate all the doubts about vague information in the given dataset.