<jupyter_start><jupyter_text>The Carpentries: Programmatic Assessment Report 2017 Year End and 2018 Q1 Authors: <NAME>, <NAME>  Part 1: Workshops over time  Workshops by Carpentry by YearWe began systematically recording data for our workshops in 2012. We can use this data to investigate how have The Carpentries have grown over the years in terms of number of workshops per year and how Data Carpentry has grown in comparison with Software Carpentry. For these analyses, we will consider only Software Carpentry and Data Carpentry workshops held between January 1st, 2012 and March 31st, 2018. Instructor Training events are analyzed in a later portion of this report.Data is collected by a team of workshop administrators.  In Africa, Australia, Canada, New Zealand, and the United Kingdom, workshop adminstrators are affiliated with our member institutions and provide in-kind staff time.  A full-time Carpentries staff member is the Workshop Administrator for the rest the world. For each of our workshops, we collected the following data:| variable | definition || -------- | ----------- | | slug | Unique identifier for each workshop. Takes the form `YYYY-MM-DD-sitename`. || start | Start date of the workshop. Takes the form `YYYY-MM-DD`. || attendance | Number of learners at the workshop. | | host_name | Institution that hosted the workshop. | | country | The two-letter country code for the country in which the workshop was held. || workshop_type | Whether this is a Software Carpentry (SWC) or Data Carpentry (DC) workshop. |<jupyter_code># Load csv into df
workshops = pd.read_csv("data_files/workshops.csv")


# Translate country codes to country names
# This does not work for Namibia (country code NA)
def get_country_name(alpha_code):
    try:
        return pycountry.countries.get(alpha_2=alpha_code).name
    except KeyError:
        return "online"

workshops['country'] = workshops['country'].apply(get_country_name)


# Clean up the tag names
workshops.loc[workshops['tag_name'].str.contains("SWC"), "workshop_type"] = "SWC"
workshops.loc[workshops['tag_name'].str.contains("DC"), "workshop_type"] = "DC"
workshops.loc[workshops['tag_name'].str.contains("LC"), "workshop_type"] = "LC"
workshops.loc[workshops['tag_name'].str.contains("TTT"), "workshop_type"] = "TTT"

workshops = workshops.drop('tag_name', axis=1)

# Clean up attendance value - this is inconsistently stored as NaN or 0
# All zero values should be NaN
workshops['attendance'] =  workshops['attendance'].replace(0.0, np.nan)

# Remove online events like maintainer onboarding 
workshops = workshops.drop(workshops[workshops.country == "online"].index)

# Date data type
workshops['start_date'] = pd.to_datetime(workshops['start_date'])

# Remove instructor training events; these will be analyzed separately.

# Limit to non TTT workshops
workshops_nonTTT = workshops[workshops['workshop_type'] != "TTT"]

workshops_nonTTT
# Note: to preview the data table, remove the semi-colon (;) from the end of the line above<jupyter_output><empty_output><jupyter_text>The Carpentries workshops by year.The table and chart below show the total number of Data Carpentry and Software Carpentry workshops held each year, including a projection for 2018 based on actual 2017 Q1 data relative to 2017 full year data. Table 1. The Carpentries workshops by year.<jupyter_code># All workshops 2012 through 2017

workshops_by_carpentry_year_through_2017 = workshops_nonTTT[workshops_nonTTT['start_date'].dt.year<=2017]
workshops_by_carpentry_year_through_2017 = workshops_by_carpentry_year_through_2017.groupby([workshops_by_carpentry_year_through_2017['start_date'].dt.year, 'workshop_type'])['slug'].count().unstack()
workshops_by_carpentry_year_through_2017['total'] = workshops_by_carpentry_year_through_2017.sum(axis=1)


# All workshops in 2017 full year and 2017 Q1
workshops_by_carpentry_year_2017 = workshops_nonTTT[workshops_nonTTT['start_date'].dt.year == 2017]
workshops_by_carpentry_year_2017Q1 = workshops_by_carpentry_year_2017[workshops_by_carpentry_year_2017['start_date'].dt.month <= 3]                                              
workshops_by_carpentry_year_2017Q1 = workshops_by_carpentry_year_2017Q1.groupby([workshops_by_carpentry_year_2017Q1['start_date'].dt.year, 'workshop_type'])['slug'].count().unstack()
workshops_by_carpentry_year_2017 = workshops_by_carpentry_year_2017.groupby([workshops_by_carpentry_year_2017['start_date'].dt.year, 'workshop_type'])['slug'].count().unstack()

# All workshops in 2018 Q1
workshops_by_carpentry_year_2018Q1 = workshops_nonTTT[workshops_nonTTT['start_date'].dt.year == 2018]
workshops_by_carpentry_year_2018Q1 = workshops_by_carpentry_year_2018Q1.groupby([workshops_by_carpentry_year_2018Q1['start_date'].dt.year, 'workshop_type'])['slug'].count().unstack()

# Proportion of 2017 workshops that took place in 2017 Q1
proportion = workshops_by_carpentry_year_2017Q1/workshops_by_carpentry_year_2017
proportion = workshops_by_carpentry_year_2017Q1/workshops_by_carpentry_year_2017
proportion = proportion.rename(index={2017:2018})

# Use proportion to calculate 2018 projections
workshops_by_carpentry_year_2018Q1_projected = workshops_by_carpentry_year_2018Q1 / proportion
workshops_by_carpentry_year_2018Q1_projected = workshops_by_carpentry_year_2018Q1_projected.astype(int)
workshops_by_carpentry_year_2018Q1_projected['total'] = workshops_by_carpentry_year_2018Q1_projected.sum(axis=1)


# Combine real data through 2017 and projected 2018 data in to one dataframe
workshops_by_carpentry_year_with_projections = pd.concat([workshops_by_carpentry_year_through_2017, workshops_by_carpentry_year_2018Q1_projected], axis=0)

# Fill na with zeros and make floats in to ints
workshops_by_carpentry_year_with_projections = workshops_by_carpentry_year_with_projections.fillna(0)
workshops_by_carpentry_year_with_projections = workshops_by_carpentry_year_with_projections.astype(int)

# Rename 2018 to projected
workshops_by_carpentry_year_with_projections.rename(index={2018: '2018 projected'}, inplace=True)



workshops_by_carpentry_year_with_projections.columns.names = ["Workshop Type"]
workshops_by_carpentry_year_with_projections.index.names = ["Year"]

workshops_by_carpentry_year_with_projections<jupyter_output><empty_output><jupyter_text>Figure 1. The Carpentries workshops by year.<jupyter_code>fig = plt.figure(figsize=(12, 6)) # Create matplotlib figure

ax = fig.add_subplot(111) # Create matplotlib axes
width = .5
title = "Carpentries workshops count by year"
workshops_by_carpentry_year_with_projections.plot(y = ["DC", "SWC"], kind='bar', ax=ax, width=width, position=1, title=title)
ax.grid(True)

ax.set_xlabel("Year")


plt.show()<jupyter_output><empty_output><jupyter_text>The number of Software Carpentry and Data Carpentry workshops appear to have remained roughly steady for the past several years, after a sharp jump from 2014 to 2015. The year 2015 was the first year in which The Carpentries had staff and a dedicated database to track workshop data, so some of this growth may instead reflect a growth in internal systems.The data shown here may not account for unreported self-organized workshops. Although The Carpentries attempts to collect data on all workshops run under "The Carpentries" brand, sometimes institutions may run a workshop without reporting it back to The Carpentries staff. Thus, if there has been a shift from more centrally-organized to more self-organized workshops, this may cause an underestimate of our workshop growth. We are working to improve data collection to have more accurate reflections of our scope of work.This may also reflect a shift to sites running a variation of Carpentries lessons, rather than official full Carpentries workshops.  While we've known anecdotally that this happens often, we have not systematically collected any data on when or how Carpentries lessons are used in other contexts.  Workshops by Carpentry by Year and CountryThe table and chart below show a count of the number of Carpentry workshops (SWC or DC) that took place in each country each year from 2012 through 2017. This data can help us to see geographic trends in our community and in particular, in which geographic regions The Carpentry community is growing.Projections for 2018 are not included here as many countries do not have enough of a history for us to base projections on. Projections for countries with a longer history or more activity are shown below.In many countries, we have seen a steady increase in the number of workshops run. In several countries though (Australia, New Zealand, Canada), we have seen a decline in the number of workshops run. This may be due to the reasons cited above, including unreported self-organized workshops or an increase in variations on Carpentries workshops.  In either case this is a motivation for The Carpentries to improve data collection and methods to understand our scope of work beyond our centrally coordinated workshops.Decreases in the number of workshops run in some countries may also be accounted for by shifts in our instructor community.  Carpentries activity is sustained by our instructors. Some instructors may move to new geographies or to new career phases. Without a larger community in place, these geographies may not have had the capacity to sustain their activity.  This is a motivation for The Carpentries to build strong and sustainable communities, with systems that account for individual turnover.  Table 2. The Carpentries workshops by year and country.The table below is a summary of all Carpentries workshops with the year and country they were held in.<jupyter_code>workshops_by_country_year = workshops_nonTTT.groupby(['country', workshops_nonTTT['start_date'].dt.year])['slug'].count().unstack()
workshops_by_country_year = workshops_by_country_year.fillna(0)
workshops_by_country_year.rename(columns={2018: '2018Q1'}, inplace=True)
workshops_by_country_year['total'] = workshops_by_country_year.sum(axis=1)
workshops_by_country_year = workshops_by_country_year.astype(int)
workshops_by_country_year

# Get all workshops for 2017
workshops_2017 = workshops_nonTTT[(workshops_nonTTT['start_date'].dt.year == 2017)]

# # Get all workshops for 2017 Q1
workshops_2017Q1 = workshops_nonTTT[(workshops_nonTTT['start_date'].dt.year == 2017) & (workshops_nonTTT['start_date'].dt.month <= 3)]

# # Get all workshops for 2018 Q1
workshops_2018Q1 = workshops_nonTTT[(workshops_nonTTT['start_date'].dt.year == 2018) & (workshops_nonTTT['start_date'].dt.month <= 3)]

# 2017 Q1 workshops by country
workshops_by_country_year_2017Q1 = workshops_2017Q1.groupby(['country', workshops_nonTTT['start_date'].dt.year])['slug'].count().unstack()
workshops_by_country_year_2017Q1.rename(columns={2017:'2017Q1'}, inplace=True)

# 2018 Q1 workshops by country
workshops_by_country_year_2018Q1 = workshops_2018Q1.groupby(['country', workshops_nonTTT['start_date'].dt.year])['slug'].count().unstack()
workshops_by_country_year_2018Q1.rename(columns={2018:'2018Q1'}, inplace=True)

# 2017 workshops by country
workshops_by_country_year_2017 = workshops_2017.groupby(['country', workshops_nonTTT['start_date'].dt.year])['slug'].count().unstack()

# Concatenated table, 2017 Q1 and 2018 Q1 workshops by country
workshops_by_country_year_1718Q1 = pd.concat([workshops_by_country_year_2017Q1, workshops_by_country_year_2018Q1, workshops_by_country_year_2017], axis=1)

# Add column to show what percent of 2017 workshops took place in 2017 Q1
workshops_by_country_year_1718Q1['prop2017Q1'] = workshops_by_country_year_1718Q1['2017Q1']/workshops_by_country_year_1718Q1[2017]

# Add column to show expected 2018 workshops, if rate is same as 2017
workshops_by_country_year_1718Q1['proj2018'] =  workshops_by_country_year_1718Q1['2018Q1']/workshops_by_country_year_1718Q1['prop2017Q1']

# Give df a better name
workshops_by_country_year_with_projections = workshops_by_country_year_1718Q1

# # Relying only on 2017 projections excludes countries that held a workshop in 2018 Q1 but not 2017 Q1
# # Adjust the 2018 projections to be the max of the 2018 projection or the 2018 Q1 actual
workshops_by_country_year_with_projections["adj_proj2018"] = workshops_by_country_year_with_projections[["proj2018", "2018Q1"]].max(axis=1)


# # Fill NA with 0 and cast floats as ints
workshops_by_country_year_with_projections = workshops_by_country_year_with_projections.fillna(0)
workshops_by_country_year_with_projections = workshops_by_country_year_with_projections.astype(int)


workshops_by_country_year_with_projections = workshops_by_country_year_with_projections.reset_index()
workshops_by_country_year_with_projections = workshops_by_country_year_with_projections.rename(columns={'index':'country'})

# Table used to calculate 2018 projections - uncomment next line to hide/show
# workshops_by_country_year_with_projections

# All countries that have held at least 10 workshops (lifetime)

workshops_by_country_year_many = workshops_by_country_year[workshops_by_country_year.total >= 10]
workshops_by_country_year_many = workshops_by_country_year_many.reset_index()
workshops_by_country_year_many = workshops_by_country_year_many.reset_index()

# Uncomment line below to show/hide table
# workshops_by_country_year_many<jupyter_output><empty_output><jupyter_text>Table 3. All countries having hosted at least 10 workshops from 2012 through 2018 (projected)<jupyter_code># Join table of countries with at least 10 workshops with 2018 projections

workshops_by_country_year_top = pd.merge(workshops_by_country_year_many, workshops_by_country_year_with_projections, on='country', how='left')
workshops_by_country_year_top = workshops_by_country_year_top[['country', 2012, 2013, 2014, 2015, 2016, '2017_x', 'adj_proj2018']]
workshops_by_country_year_top = workshops_by_country_year_top.rename(columns={'2017_x':2017, 'adj_proj2018':'proj2018'})

workshops_by_country_year_top = workshops_by_country_year_top.fillna(0)
workshops_by_country_year_top['proj2018'] = workshops_by_country_year_top['proj2018'].astype(int)
workshops_by_country_year_top<jupyter_output><empty_output><jupyter_text>Figure 3.   All countries having hosted at least 10 workshops from 2012 through 2018 (projected)<jupyter_code># Draw bar chart showing most active countries and workshops by year# Draw b 

workshops_by_country_year_top = workshops_by_country_year_top[::-1]
workshops_by_country_year_top = workshops_by_country_year_top.set_index('country')

fig = plt.figure(figsize=(12, 10)) # Create matplotlib figure

ax = fig.add_subplot(111) # Create matplotlib axes

title = "Carpentries workshops by country by year"
workshops_by_country_year_top.plot(y = list(workshops_by_country_year_top)[::-1], kind='barh', ax=ax, position=1, title=title)

ax.grid(True)

handles, labels = ax.get_legend_handles_labels()
ax.legend(handles[::-1], labels[::-1], title='Year', loc='upper right')

plt.show()

workshops_by_country_year_top
### When did each country have its first workshop?

# Data goes back to 2012; 
# some countries may have been holding Carpentries workshops before this time.


w2 = workshops_by_country_year.copy()

# See https://stackoverflow.com/questions/31828240/first-non-null-value-per-row-from-a-list-of-pandas-columns
# and
# https://stackoverflow.com/questions/38467749/find-first-non-zero-value-in-each-row-of-pandas-dataframe

# Fill zero values with Nan to make them non valid values
res = w2[w2 != 0.0]

first_workshop = res.apply(pd.Series.first_valid_index, axis=1)
first_workshop = pd.DataFrame({'year': first_workshop.values.astype(str), 'country': first_workshop.index.values}, index=None)
first_workshop = first_workshop.sort_values('year').set_index('year')
# first_workshop

### How many countries had their first workshop each year?

first_workshop.groupby(level=0).count()<jupyter_output><empty_output><jupyter_text>Which countries had their first workshop in 2017?Four countries, all in Africa, had their first workshop in 2017. This is representative of the significant growth we've seen in this part of the world. (Include information about new workshop administrators and task force in Africa)<jupyter_code>first_workshop.loc['2017']

first_workshop<jupyter_output><empty_output><jupyter_text>Attendance at workshops  Workshops missing attendanceAfter running a workshop, The Carpentries staff asks hosts or instructors to submit attendance data to us.  In some cases, this is detailed account of the number registered and the number attended each day.  In other cases, this is a best estimate by the host or instructors. In some cases, the hosts or instructors do not report back on attendance data. From 2012 through 2018 Q1, 122 of 1323 (about 9%) workshops were missing attendance.  While this is not a large percentage of total workshops, it is indication for The Carpentries to understand why attendance data may not have been shared. It could be because the hosts or instructors were uncomfortable sharing low numbers, because the hosts or instructors didn't have clear systems to collect and share attendance numbers, or other reaons.  The Carpentries should better understand these reasons in order to address them.For the analyses below, workshops missing attendance are excluded from the analyses. Because low attendance is a possible factor in not having reported attendance, replacing missing attendance data with means would not be an accurate reflection of our numbers.<jupyter_code># This is a full list of all workshops that did not report attendance 
# back to the Carpentries.  Reasons for this may vary and demonstrate a 
# need for Carpentries staff, workshop hosts, and instructors to better 
# collect and record this data.


# Not sure why this way gives a warning
# missing_attendance = workshops_nonTTT[pd.isnull(workshops['attendance'])]

# This way does not give warning
missing_attendance = workshops[workshops.attendance.isnull()]

# missing_attendance
# Uncomment the above line to see a list of workshops missing attendance
# Calculate projected attendance for 2018

# Total attendance for 2017
total2017 = workshops_2017['attendance'].sum(skipna=True)

# Total attendance for 2017 Q1
total2017Q1 = workshops_2017Q1['attendance'].sum(skipna=True)


# Total attendance for 2018 Q1
total2018Q1 = workshops_2018Q1['attendance'].sum(skipna=True)


proportion_attendance_2017Q1 = total2017Q1/total2017

projected_attendance_2018 = total2018Q1/proportion_attendance_2017Q1<jupyter_output><empty_output><jupyter_text>Total attendance by year: Table viewThis table and chart show the total number of learners at Carpentries workshops each year.  Based on the number of learners in the first quarter of 2017 relative to the full year, we anticipate a drop in the number of learners we will see in 2018.<jupyter_code>attendance = workshops_nonTTT[['start_date', 'attendance']]   #df['date'].dt.year
attendance = attendance.assign(year=attendance['start_date'].dt.year)

# Doing the above like this gives a SettingWithCopyWarning error
# attendance['year'] = attendance['start_date'].dt.year
# See https://stackoverflow.com/questions/12555323/adding-new-column-to-existing-dataframe-in-python-pandas
total_attendance_by_year = attendance.groupby('year')['attendance'].sum()
total_attendance_by_year = total_attendance_by_year.to_frame()

total_attendance_by_year.loc['2018proj'] = [projected_attendance_2018]
total_attendance_by_year = total_attendance_by_year.astype(int)
total_attendance_by_year = total_attendance_by_year.drop([2018])
total_attendance_by_year<jupyter_output><empty_output><jupyter_text>Total attendance by year: Chart view<jupyter_code>fig = plt.figure(figsize=(10, 8)) # Create matplotlib figure

ax = fig.add_subplot(111) # Create matplotlib axes

title = "Carpentries attendance by year"
total_attendance_by_year.plot(kind='bar', ax=ax, title=title, legend=False)

ax.grid(True)

plt.show()<jupyter_output><empty_output><jupyter_text>Box plot showing frequency of class sizesThis box plot shows the range of our class sizes, and demonstrates a trend towards smaller classes in the past few years, with 2018 workshops showing a slight increase. The center line represents the median and the red dot represents the mean.  In this case, 2018 data is not a projection but rather actual data from 2018 Q1.<jupyter_code># See https://pandas.pydata.org/pandas-docs/stable/generated/pandas.DataFrame.boxplot.html

title = "Frequency of class sizes by year"

has_attendance = attendance[pd.notnull(attendance['attendance'])]

meanprops = dict(marker='o', markeredgecolor='black', markerfacecolor='firebrick')
boxprops = dict(linestyle='-', linewidth=2, color='k')

ax = has_attendance.boxplot(by="year", figsize=(10,8), showmeans = True, meanprops = meanprops, boxprops = boxprops)
ax.set_title(title)
ax.get_figure().suptitle("")
plt.show()<jupyter_output><empty_output><jupyter_text>Binned attendance by yearThis table shows by year, how many workshops had 1-10 learners, 11-20 learners, and so on.<jupyter_code>bins = pd.cut(attendance['attendance'], [1, 10, 20, 30, 40, 50, 60, 70, 80, 90, 100, np.inf])
attendance.groupby(bins)['attendance'].agg(['count'])

binned_attendance_by_year = attendance.groupby([ bins, 'year'])['attendance'].agg(['count'])

binned_attendance_by_year =  binned_attendance_by_year.unstack()
binned_attendance_by_year['total'] = binned_attendance_by_year.sum(axis=1)
binned_attendance_by_year = binned_attendance_by_year.rename(index=str, columns={2018:"2018Q1"})

binned_attendance_by_year = binned_attendance_by_year.fillna(0).astype(int)
binned_attendance_by_year<jupyter_output><empty_output><jupyter_text>Instructor TrainingThis tables lists all instructor training events the Carpentries has held since 2012. The "count_badged" column is a total of all individuals from that event with at least one badge.  We are not distinguishing between Software Carpentry and Data Carpentry badges.For reference, all training events through 2018 Q1 are listed here. However, the analyses below exclude data from 2018 Q1.  Trainees have 90 days to complete their certification requirements, so no one who attended instructor training in 2018 Q1 would be expected to have completed certification.For each of our instructor training events, we collected the following data:| variable | definition || -------- | ----------- | | slug | Unique identifier for each workshop. Takes the form `YYYY-MM-DD-sitename`. || start | Start date of the workshop. Takes the form `YYYY-MM-DD`. || country | The country in which the workshop was held. Online events are noted as "online" even if all participants were in one country. || attendance | Number of trainees at the workshop. | | count_badged | Number of trainees awarded a Software Carpentry (SWC) or Data Carpentry (DC) badge. * || pct_completion | Percent of trainees awarded a Software Carpentry (SWC) or Data Carpentry (DC) badge. * |\* While we grant both Software Carpentry and Data Carpentry badges we do not distinguish between them for teaching eligibility or any other status within the Carpentries.<jupyter_code>badges_by_training_event = pd.read_csv("data_files/badges_by_training_event.csv")



badges_by_training_event['pct_completion'] = badges_by_training_event['count_badged'] / badges_by_training_event['attendance'] * 100
badges_by_training_event = badges_by_training_event.round({'pct_completion':1})
badges_by_training_event['start'] = pd.to_datetime(badges_by_training_event['start'])

badges_by_training_event_thru2017 = badges_by_training_event[badges_by_training_event['start'].dt.year < 2018]


badges_by_training_event['country'] = badges_by_training_event['country'].apply(get_country_name)

badges_by_training_event_thru2017 = badges_by_training_event[badges_by_training_event['start'].dt.year < 2018]


badges_by_training_event<jupyter_output><empty_output><jupyter_text>Number of instructor training events by countryThis table shows how many training events took place in each country.  Inperson events are noted as taking place in a specific country. All online events are noted as online, even if the trainees came entirely from one country.<jupyter_code>trainings_by_country = badges_by_training_event_thru2017.groupby([badges_by_training_event['start'].dt.year, 'country'])['slug'].count().unstack()
trainings_by_country['total'] = trainings_by_country.sum(axis=1)
trainings_by_country.index.values
trainings_by_country = trainings_by_country.rename(index = {2018:'2018Q1'})
trainings_by_country = trainings_by_country.fillna(0)
trainings_by_country = trainings_by_country.astype(int)

trainings_by_country<jupyter_output><empty_output><jupyter_text>Binned completion rates for instructor training events by yearThis shows the count of events by completion rates.  For example, six events in 2016 had a completion rate under 25%.  The counts are binned by quartiles, separating out events with 100% completion rates.Training events from 2018 are excluded from this table. Because trainees have 90 days to complete their certification requirements, no trainee from a 2018 Q1 event would be expected to have completed their certification requirements.Two events have had 100% completion rate:  an online event in 2015, bringing together individual trainees across Europe, and a 2017 in-person event at Stanford University in California, USA. The Carpentries can review the coordination and support systems associated with events like these to understand how we can best support trainees throughout the certification process.  Likewise, 13 events total has a completion rate under 25%.  The Carpentries can review how trainees at these events could have been better supported in this process and welcomed into the Carpentries community.<jupyter_code>bins = pd.cut(badges_by_training_event_thru2017['pct_completion'], [0, 25, 50, 75, 99, 100])
binned_badges_by_year = badges_by_training_event_thru2017.groupby([ bins, badges_by_training_event_thru2017['start'].dt.year])['pct_completion'].agg(['count'])

binned_badges_by_year =  binned_badges_by_year.unstack()
binned_badges_by_year['total'] = binned_badges_by_year.sum(axis=1)
binned_badges_by_year

binned_badges_by_year.fillna(0, inplace=True)
binned_badges_by_year = binned_badges_by_year.astype(int)
binned_badges_by_year<jupyter_output><empty_output><jupyter_text>Attendance and number badged by year Total attendance by yearThis table shows the total number of trainees at instructor training events each year by country.<jupyter_code>trainees_by_year = badges_by_training_event_thru2017.groupby([badges_by_training_event['start'].dt.year, 'country'])['attendance'].sum().unstack()
trainees_by_year.fillna(0, inplace=True)
trainees_by_year = trainees_by_year.astype(int)
trainees_by_year
# trainigs_by_country = badges_by_training_event.groupby([badges_by_training_event['start_date'].dt.year, 'country'])['slug'].count().unstack()<jupyter_output><empty_output><jupyter_text>Total badged by yearThis table shows the total number of trainees who were awarded badges at instructor training events each year by country.<jupyter_code>badges_by_year = badges_by_training_event_thru2017.groupby([badges_by_training_event['start'].dt.year, 'country'])['count_badged'].sum().unstack()
badges_by_year.fillna(0, inplace=True)
badges_by_year = badges_by_year.astype(int)
badges_by_year<jupyter_output><empty_output><jupyter_text>Percent of trainees badged per yearThis is calculated from the above two tables. The year is the year the event was held, not the year the badge was issued.<jupyter_code>percent_badged_by_year = badges_by_year/trainees_by_year*100
percent_badged_by_year = percent_badged_by_year.round(1)
percent_badged_by_year.fillna(0, inplace = True)
percent_badged_by_year = percent_badged_by_year.astype(int)
percent_badged_by_year<jupyter_output><empty_output><jupyter_text>Comparison:  Inperson vs Online Training EventsThe table and char below show the number of training events we have held each year, separated by online and inperson events.  Until 2015, our training events were conducted exclusively on line by the founder of Software Carpentry.  In recent years, we have built a community of instructor trainers who are avaible to teach online events as well as inperson events at their own sites.  We have also formalized and strengthened our institutional membership program, where higher level memberships entitle the member site to inperson training events. Number of events per year<jupyter_code>countries = list(trainings_by_country)
countries.remove('online')
countries.remove('total')
inperson_trainings = trainings_by_country[countries].sum(axis = 1).to_frame()

online_trainings = trainings_by_country[['online']]

trainings_online_inperson = pd.concat([inperson_trainings, online_trainings], axis=1)

trainings_online_inperson.rename(columns = {0:'in-person'}, inplace=True)

trainings_online_inperson.fillna(0, inplace=True)
trainings_online_inperson = trainings_online_inperson.astype(int)
trainings_online_inperson

fig = plt.figure(figsize=(10, 8)) # Create matplotlib figure

ax = fig.add_subplot(111) # Create matplotlib axes

title = "Carpentries instructor training events by year"
ax = trainings_online_inperson[['online', 'in-person']].plot(kind='bar', ax=ax, stacked=False, title=title)
ax.grid(True)
plt.show()<jupyter_output><empty_output><jupyter_text>Completion rates (percentages)The table below shows the completion rates of online instructor training events compared to inperson events. In 2012, we ran only two training events, both online. While this was early in The Carpentries' lifecycle, this may have allowed us to target trainees vested in our mission.  Requirements for certification have also changed over the years; the high certification rate in 2012 may also reflect more lenient certification requirements.In our first year of running inperson training events (2015) we saw completion rates were significantly lower for inperson events than for online events. This may have been due to us not yet having recognized different ways in engaging with communities after running in person training events.  In the following years, completion rates for online and inperson instructor training events are comparable. Many of our inperson training events are run by Trainers already affiliated with the host site and have a commitment to building a strong instructor community at their site.  Many of these events are also run through our formalized institutional membership program rather than as stand alone events. Through the institutional membership, host sites become connected with the Carpentries community and receive support from Carpentries staff.<jupyter_code>countries = list(trainees_by_year)

countries.remove('online')

inperson_trainees = trainees_by_year[countries].sum(axis=1).to_frame()
inperson_badges = badges_by_year[countries].sum(axis=1).to_frame()


online_trainees = trainees_by_year[['online']]
online_badges = badges_by_year[['online']]

pct_completion_online = online_badges/online_trainees
pct_completion_inperson = inperson_badges/inperson_trainees
pct_completion_online_inperson = pd.concat([pct_completion_online, pct_completion_inperson], axis = 1)
pct_completion_online_inperson.rename(columns={0:'in-person'}, inplace=True)
pct_completion_online_inperson = pct_completion_online_inperson.fillna(0)
pct_completion_online_inperson = pct_completion_online_inperson * 100
pct_completion_online_inperson = pct_completion_online_inperson.astype(int)


pct_completion_online_inperson = pct_completion_online_inperson.replace(0, "-")

pct_completion_online_inperson
fig = plt.figure(figsize=(10, 8)) # Create matplotlib figure

ax = fig.add_subplot(111) # Create matplotlib axes

# Make dashes into numbers again so numeric data can be ploted
pct_completion_online_inperson_chart = pct_completion_online_inperson.replace("-", 0)

title = "Carpentries instructor training events completion rates (percentages)"
ax = pct_completion_online_inperson_chart[['in-person', 'online']].plot(kind='bar', ax=ax, stacked=False, title=title)
ax.grid(True)
plt.show()<jupyter_output><empty_output><jupyter_text>We continued to look at progress from going through instructor training to completing certification and getting badged on to when instructors taught their first workshop.  All dates are expressed as the first of the month. Exact dates are masked to preserve anonymity.For each certified instructor, we collected the following data:| variable | definition || -------- | ----------- | | date_awarded | The first day of the month the badge was awarded.  || first_wkshp | The first day of the month this instructor taught their first workshop. || days | A calculated field representing the difference between these two dates. |The Carpentries requires that all centrally organized workshops are taught by certified Carpentries instructors. However, self organized workshops may be taught by one certified instructor who works with a peer or colleague as a co-instructors.  Many of these people go on to complete our instructor training program and become certified instructors themselves, which is why we see some people teaching even years before their certification date.<jupyter_code>days_badge_to_teach = pd.read_csv("data_files/days_badge_to_teach.csv")


days_badge_to_teach['date_awarded'] = pd.to_datetime(days_badge_to_teach['date_awarded'])
days_badge_to_teach['first_wkshp'] = pd.to_datetime(days_badge_to_teach['first_wkshp'])
days_badge_to_teach['badge_year'] = days_badge_to_teach['date_awarded'].dt.year

days_badge_to_teach = days_badge_to_teach[days_badge_to_teach['badge_year'] < 2018]

days_badge_to_teach<jupyter_output><empty_output><jupyter_text>This table shows for badges issued each year, how many days after receiving the badge the instructor taught their first workshop in 90-day bins.  The first row shows the number of people badged that year that had already taught their first workshop.No badges were issued before December 2012.  This was when we officially started issuing badges to people with a history with Software Carpentry.Detailed narrative coming. Bullets* Many more trainees have previous Carpentries teaching experience than expected* Good to know we are engaging people in his way, would also like to think about the people who have not taught a workshop after getting badged.  Does the lack of experience keep them from pursuing teaching opportunities?This table does not yet account for badged instructors who have never taught. (coming soon)<jupyter_code>bins = pd.cut(days_badge_to_teach['days'], [-2000, 0, 90, 180, 360, np.inf])
days_badge_to_teach.groupby(bins)['days'].agg(['count'])

binned_days = days_badge_to_teach.groupby([bins, 'badge_year'])['days'].agg(['count'])

binned_days =  binned_days.unstack()

binned_days.fillna(0, inplace=True)


binned_days = binned_days.astype(int)
binned_days
binned_days.index.values
# Remove bar chart - box plot seems to be more informative.

# fig = plt.figure(figsize=(15, 8)) # Create matplotlib figure

# ax = fig.add_subplot(111) # Create matplotlib axes

# xticks = ['before', '3 mos', '6 mos', '12 mos', 'more than 1 year']

# title = "Days from badging to teaching by year"
# binned_days.plot(y = list(binned_days[::-1]), kind='bar', ax=ax, position=1, title=title)


# plt.show()

title = "Days from badging to teaching by year"

# has_attendance = attendance[pd.notnull(attendance['attendance'])]

meanprops = dict(marker='o', markeredgecolor='black', markerfacecolor='firebrick')
boxprops = dict(linestyle='-', linewidth=2, color='k')

ax = days_badge_to_teach.boxplot(by="badge_year", figsize=(10,8), showmeans = True, meanprops = meanprops, boxprops = boxprops)
ax.set_title(title)
ax.get_figure().suptitle("")
plt.show()<jupyter_output><empty_output><jupyter_text>Instructor trainersUntil 2016, all Instructor Training events were run by Software Carpentry founder and former Executive Director <NAME>. This limited our growth...In 2016, The Carpentries launched a training program for instructor trainers.(narrative to be developed - bullets below)* Allowed us to expand reach by running several events a month, across timezones for online events* Allowed us to build capacity at member organizations who have onsite Instructor Trainers, run events for their site building a community of instructors there, these instructors also have onsite support to run workshops* Trainers in African and European countries overlap timezones allowing them to connect with a wider audience* Allowed us to reach new geographies - new group of Trainers in Africa* Connect this back to increase in number of training events and new instructors* Another Trainers training event is being planned for late 2018; anticipate same growth rate as 2017.<jupyter_code>trainers = pd.read_csv("data_files/trainers.csv")
trainers['awarded'] = pd.to_datetime(trainers['awarded'])
trainers['country'] = trainers['country'].apply(get_country_name)

# Backwards way of fixing the NA/Namibia issue
trainers.replace('online', 'Namibia', inplace = True)

trainers<jupyter_output><empty_output><jupyter_text>New Instructor Trainers by Year<jupyter_code>trainers_by_year = trainers.groupby(trainers['awarded'].dt.year).id.count().to_frame()

trainers_by_year
fig = plt.figure(figsize=(15, 8)) # Create matplotlib figure

ax = fig.add_subplot(111) # Create matplotlib axes

title = "New badged Trainers by year"
trainers_by_year.plot(kind='bar', ax=ax, position=1, title=title)

plt.show()<jupyter_output><empty_output><jupyter_text>New Instructor Trainers by Country<jupyter_code>trainers_by_country = trainers.groupby(trainers['country']).id.count().to_frame()

trainers_by_country
fig = plt.figure(figsize=(15, 8)) # Create matplotlib figure

ax = fig.add_subplot(111) # Create matplotlib axes


title = "New badged Trainers by Country"
trainers_by_country.plot(kind='bar', ax=ax, position=1, title=title, legend=False, grid=False)


plt.show()
trainers_by_country_year = trainers.groupby(['country', trainers['awarded'].dt.year])['id'].count().to_frame().unstack()

# This transformation prevents plot below from being drawn
# Needs to come after plot
# trainers_by_country_year = trainers_by_country_year.fillna(0)
# trainers_by_country_year = trainers_by_country_year.astype(int)
# trainers_by_country_year = trainers_by_country_year.replace(0, "-")
# trainers_by_country_year.reset_index()

trainers_by_country_year
fig = plt.figure(figsize=(10, 6)) # Create matplotlib figure

ax = fig.add_subplot(111) # Create matplotlib axes

title = "New Badged Trainers by Country by Year"
trainers_by_country_year.plot(y = list(trainers_by_country_year)[::-1], kind='barh', ax=ax, position=1, title=title)

ax.grid(True)

plt.show()<jupyter_output><empty_output>
{'fraction_non_alphanumeric': 0.08075800996566236, 'fraction_numerical': 0.029845351509049132, 'mean_word_length': 6.877567622534065, 'pattern_counts': {'":': 0, '<': 111, '<?xml version=': 0, '>': 105, 'https://': 4, 'lorem ipsum': 0, 'www.': 0, 'xml': 0}, 'pii_count': 0, 'substrings_counts': 0, 'word_list_counts': {'cursed_substrings.json': 6, 'profanity_word_list.json': 0, 'sexual_word_list.json': 0, 'zh_pornsignals.json': 0}}
{'chain_length': '25', 'dir': 'jupyter-structured-clean-dedup', 'id': '99138', 'license': 'CC-BY-4.0', 'n_tokens_mistral': 12454, 'n_tokens_neox': 11358, 'n_words': 3599, 'path': 'programmatic-assessment/workshops/archived_notebooks/final_report_draft.ipynb', 'repo_name': 'ErinBecker/assessment', 'size': '38750'}