pvlib-python#

[1]:
import utils
import pandas as pd

from bokeh.plotting import figure, show
from bokeh.models import HoverTool
from bokeh.transform import jitter
from bokeh.io import output_notebook
[2]:
output_notebook()
Loading BokehJS ...

ReadTheDocs#

[3]:
df = utils.get_rtd_analytics_data('pvlib')
data_period = df['Date'].min().strftime('%Y-%m-%d') + ' to ' + df['Date'].max().strftime('%Y-%m-%d')
---------------------------------------------------------------------------
ParserError                               Traceback (most recent call last)
File /opt/hostedtoolcache/Python/3.11.17/x64/lib/python3.11/site-packages/pandas/_libs/tslib.pyx:606, in pandas._libs.tslib.array_to_datetime()

File /opt/hostedtoolcache/Python/3.11.17/x64/lib/python3.11/site-packages/pandas/_libs/tslibs/parsing.pyx:321, in pandas._libs.tslibs.parsing.parse_datetime_string()

File /opt/hostedtoolcache/Python/3.11.17/x64/lib/python3.11/site-packages/dateutil/parser/_parser.py:1368, in parse(timestr, parserinfo, **kwargs)
   1367 else:
-> 1368     return DEFAULTPARSER.parse(timestr, **kwargs)

File /opt/hostedtoolcache/Python/3.11.17/x64/lib/python3.11/site-packages/dateutil/parser/_parser.py:643, in parser.parse(self, timestr, default, ignoretz, tzinfos, **kwargs)
    642 if res is None:
--> 643     raise ParserError("Unknown string format: %s", timestr)
    645 if len(res) == 0:

ParserError: Unknown string format: HTTP/1.1 500 Internal Server Error

During handling of the above exception, another exception occurred:

TypeError                                 Traceback (most recent call last)
File /opt/hostedtoolcache/Python/3.11.17/x64/lib/python3.11/site-packages/pandas/_libs/tslib.pyx:617, in pandas._libs.tslib.array_to_datetime()

TypeError: invalid string coercion to datetime for "HTTP/1.1 500 Internal Server Error" at position 1535

During handling of the above exception, another exception occurred:

ParserError                               Traceback (most recent call last)
Cell In[3], line 1
----> 1 df = utils.get_rtd_analytics_data('pvlib')
      2 data_period = df['Date'].min().strftime('%Y-%m-%d') + ' to ' + df['Date'].max().strftime('%Y-%m-%d')

File ~/work/pv-foss-engagement/pv-foss-engagement/sphinx/source/project/utils.py:23, in get_rtd_analytics_data(project)
     21 dfs = [pd.read_csv(fn, parse_dates=True) for fn in filenames]
     22 df = pd.concat(dfs)
---> 23 df['Date'] = pd.to_datetime(df['Date'])
     24 # the last day in a file can be incomplete, so in the overlap range we
     25 # prefer newer records (indicated by occurring later in `filenames`)
     26 df = df.drop_duplicates(['Date', 'Version', 'Path'], keep='last')

File /opt/hostedtoolcache/Python/3.11.17/x64/lib/python3.11/site-packages/pandas/core/tools/datetimes.py:1065, in to_datetime(arg, errors, dayfirst, yearfirst, utc, format, exact, unit, infer_datetime_format, origin, cache)
   1063             result = arg.tz_localize(tz)
   1064 elif isinstance(arg, ABCSeries):
-> 1065     cache_array = _maybe_cache(arg, format, cache, convert_listlike)
   1066     if not cache_array.empty:
   1067         result = arg.map(cache_array)

File /opt/hostedtoolcache/Python/3.11.17/x64/lib/python3.11/site-packages/pandas/core/tools/datetimes.py:230, in _maybe_cache(arg, format, cache, convert_listlike)
    228 unique_dates = unique(arg)
    229 if len(unique_dates) < len(arg):
--> 230     cache_dates = convert_listlike(unique_dates, format)
    231     # GH#45319
    232     try:

File /opt/hostedtoolcache/Python/3.11.17/x64/lib/python3.11/site-packages/pandas/core/tools/datetimes.py:439, in _convert_listlike_datetimes(arg, format, name, tz, unit, errors, infer_datetime_format, dayfirst, yearfirst, exact)
    437 assert format is None or infer_datetime_format
    438 utc = tz == "utc"
--> 439 result, tz_parsed = objects_to_datetime64ns(
    440     arg,
    441     dayfirst=dayfirst,
    442     yearfirst=yearfirst,
    443     utc=utc,
    444     errors=errors,
    445     require_iso8601=require_iso8601,
    446     allow_object=True,
    447 )
    449 if tz_parsed is not None:
    450     # We can take a shortcut since the datetime64 numpy array
    451     # is in UTC
    452     dta = DatetimeArray(result, dtype=tz_to_dtype(tz_parsed))

File /opt/hostedtoolcache/Python/3.11.17/x64/lib/python3.11/site-packages/pandas/core/arrays/datetimes.py:2178, in objects_to_datetime64ns(data, dayfirst, yearfirst, utc, errors, require_iso8601, allow_object, allow_mixed)
   2176 order: Literal["F", "C"] = "F" if flags.f_contiguous else "C"
   2177 try:
-> 2178     result, tz_parsed = tslib.array_to_datetime(
   2179         data.ravel("K"),
   2180         errors=errors,
   2181         utc=utc,
   2182         dayfirst=dayfirst,
   2183         yearfirst=yearfirst,
   2184         require_iso8601=require_iso8601,
   2185         allow_mixed=allow_mixed,
   2186     )
   2187     result = result.reshape(data.shape, order=order)
   2188 except OverflowError as err:
   2189     # Exception is raised when a part of date is greater than 32 bit signed int

File /opt/hostedtoolcache/Python/3.11.17/x64/lib/python3.11/site-packages/pandas/_libs/tslib.pyx:428, in pandas._libs.tslib.array_to_datetime()

File /opt/hostedtoolcache/Python/3.11.17/x64/lib/python3.11/site-packages/pandas/_libs/tslib.pyx:684, in pandas._libs.tslib.array_to_datetime()

File /opt/hostedtoolcache/Python/3.11.17/x64/lib/python3.11/site-packages/pandas/_libs/tslib.pyx:830, in pandas._libs.tslib._array_to_datetime_object()

File /opt/hostedtoolcache/Python/3.11.17/x64/lib/python3.11/site-packages/pandas/_libs/tslib.pyx:820, in pandas._libs.tslib._array_to_datetime_object()

File /opt/hostedtoolcache/Python/3.11.17/x64/lib/python3.11/site-packages/pandas/_libs/tslibs/parsing.pyx:321, in pandas._libs.tslibs.parsing.parse_datetime_string()

File /opt/hostedtoolcache/Python/3.11.17/x64/lib/python3.11/site-packages/dateutil/parser/_parser.py:1368, in parse(timestr, parserinfo, **kwargs)
   1366     return parser(parserinfo).parse(timestr, **kwargs)
   1367 else:
-> 1368     return DEFAULTPARSER.parse(timestr, **kwargs)

File /opt/hostedtoolcache/Python/3.11.17/x64/lib/python3.11/site-packages/dateutil/parser/_parser.py:643, in parser.parse(self, timestr, default, ignoretz, tzinfos, **kwargs)
    640 res, skipped_tokens = self._parse(timestr, **kwargs)
    642 if res is None:
--> 643     raise ParserError("Unknown string format: %s", timestr)
    645 if len(res) == 0:
    646     raise ParserError("String does not contain a date: %s", timestr)

ParserError: Unknown string format: HTTP/1.1 500 Internal Server Error present at position 1535
[4]:
total_by_version = df.groupby('Version')['Views'].sum().reset_index()

p = figure(x_range=total_by_version['Version'], height=350, tooltips=[("Version", "@Version"), ("Views", "@Views")],
           title=f"Page views by RTD version ({data_period})",)
p.vbar(x='Version', top='Views', width=0.75, source=total_by_version,
       line_color='white')
p.xaxis.major_label_orientation = 3.14/2
p.yaxis.axis_label = 'Total page views'
show(p)
---------------------------------------------------------------------------
NameError                                 Traceback (most recent call last)
Cell In[4], line 1
----> 1 total_by_version = df.groupby('Version')['Views'].sum().reset_index()
      3 p = figure(x_range=total_by_version['Version'], height=350, tooltips=[("Version", "@Version"), ("Views", "@Views")],
      4            title=f"Page views by RTD version ({data_period})",)
      5 p.vbar(x='Version', top='Views', width=0.75, source=total_by_version,
      6        line_color='white')

NameError: name 'df' is not defined
[5]:
daily_views = df.pivot_table(values='Views', index='Date', columns='Version', aggfunc='sum')[['stable', 'latest']].fillna(0)

p = figure(x_axis_type="datetime", height=350, title=f"Daily views by RTD version")
hover_tool = HoverTool(tooltips=[('Date', '@x{%Y-%m-%d}'), ('Views', '@y')],
                       formatters={'@x': 'datetime'})
hover_tool.point_policy = 'snap_to_data'
p.add_tools(hover_tool)
p.line(daily_views.index, daily_views['stable'], legend_label='stable')
p.line(daily_views.index, daily_views['latest'], legend_label='latest', color='#ff7f0e')
p.yaxis.axis_label = 'Daily page views'
show(p)
---------------------------------------------------------------------------
NameError                                 Traceback (most recent call last)
Cell In[5], line 1
----> 1 daily_views = df.pivot_table(values='Views', index='Date', columns='Version', aggfunc='sum')[['stable', 'latest']].fillna(0)
      3 p = figure(x_axis_type="datetime", height=350, title=f"Daily views by RTD version")
      4 hover_tool = HoverTool(tooltips=[('Date', '@x{%Y-%m-%d}'), ('Views', '@y')],
      5                        formatters={'@x': 'datetime'})

NameError: name 'df' is not defined
[6]:
annual = df.loc[df['Version'] == 'stable', ['Date', 'Views']].set_index('Date')['Views'].resample('a').sum()
views = pd.DataFrame({'year': annual.index.year, 'views': annual})

p = figure(height=350, tooltips=[("Annual page views", "@views"), ("Year", "@year")],
           title="Annual page views")
p.vbar(x='year', top='views', width=0.75, source=views)
p.yaxis.axis_label = 'Annual page views'
show(p)
---------------------------------------------------------------------------
NameError                                 Traceback (most recent call last)
Cell In[6], line 1
----> 1 annual = df.loc[df['Version'] == 'stable', ['Date', 'Views']].set_index('Date')['Views'].resample('a').sum()
      2 views = pd.DataFrame({'year': annual.index.year, 'views': annual})
      4 p = figure(height=350, tooltips=[("Annual page views", "@views"), ("Year", "@year")],
      5            title="Annual page views")

NameError: name 'df' is not defined
[7]:
# restrict to v0.9.1 and forward since that's when all the URLs changed
df2 = df.loc[(df['Date'] > '2022-03-30') & (df['Version'] == 'stable'), :].copy()
pathviews = df2.groupby('Path')['Views'].sum().reset_index()
---------------------------------------------------------------------------
NameError                                 Traceback (most recent call last)
Cell In[7], line 2
      1 # restrict to v0.9.1 and forward since that's when all the URLs changed
----> 2 df2 = df.loc[(df['Date'] > '2022-03-30') & (df['Version'] == 'stable'), :].copy()
      3 pathviews = df2.groupby('Path')['Views'].sum().reset_index()

NameError: name 'df' is not defined
[8]:
n = 20
subset = pathviews.sort_values('Views', ascending=False)[:n]
p = figure(y_range=subset['Path'], height=400, tooltips=[("URL", "@Path"), ("Views", "@Views")],
           title=f"Views by URL (Top {n}, {data_period})")
p.hbar(y='Path', right='Views', source=subset, height=0.75,
       line_color='white')
p.xaxis.axis_label = 'Total page views'
show(p)
---------------------------------------------------------------------------
NameError                                 Traceback (most recent call last)
Cell In[8], line 2
      1 n = 20
----> 2 subset = pathviews.sort_values('Views', ascending=False)[:n]
      3 p = figure(y_range=subset['Path'], height=400, tooltips=[("URL", "@Path"), ("Views", "@Views")],
      4            title=f"Views by URL (Top {n}, {data_period})")
      5 p.hbar(y='Path', right='Views', source=subset, height=0.75,
      6        line_color='white')

NameError: name 'pathviews' is not defined
[9]:
prefixes = ['gallery', 'reference', 'user_guide']
pathviews['Prefix'] = pathviews['Path'].str.split("/").str[1]
groupviews = pathviews.loc[pathviews['Prefix'].isin(prefixes), :].groupby('Prefix')['Views'].sum().loc[prefixes].sort_values().reset_index()

p = figure(x_range=groupviews['Prefix'], height=350, tooltips=[("Section", "@Prefix"), ("Views", "@Views")],
           title=f"Page views by docs section ({data_period})")
p.vbar(x='Prefix', top='Views', width=0.75, source=groupviews)
p.yaxis.axis_label = 'Total page views'
show(p)
---------------------------------------------------------------------------
NameError                                 Traceback (most recent call last)
Cell In[9], line 2
      1 prefixes = ['gallery', 'reference', 'user_guide']
----> 2 pathviews['Prefix'] = pathviews['Path'].str.split("/").str[1]
      3 groupviews = pathviews.loc[pathviews['Prefix'].isin(prefixes), :].groupby('Prefix')['Views'].sum().loc[prefixes].sort_values().reset_index()
      5 p = figure(x_range=groupviews['Prefix'], height=350, tooltips=[("Section", "@Prefix"), ("Views", "@Views")],
      6            title=f"Page views by docs section ({data_period})")

NameError: name 'pathviews' is not defined
[10]:
subset = pathviews.loc[pathviews['Prefix'].isin(prefixes), :]

p = figure(x_range=prefixes, height=350, tooltips=[("URL", "@Path"), ("Views", "@Views")],
           title=f"Page views by docs page ({data_period})")
p.scatter(x=jitter('Prefix', width=0.1, range=p.x_range, distribution='normal'),
          y='Views', source=subset)
p.yaxis.axis_label = 'Total page views'
show(p)
---------------------------------------------------------------------------
NameError                                 Traceback (most recent call last)
Cell In[10], line 1
----> 1 subset = pathviews.loc[pathviews['Prefix'].isin(prefixes), :]
      3 p = figure(x_range=prefixes, height=350, tooltips=[("URL", "@Path"), ("Views", "@Views")],
      4            title=f"Page views by docs page ({data_period})")
      5 p.scatter(x=jitter('Prefix', width=0.1, range=p.x_range, distribution='normal'),
      6           y='Views', source=subset)

NameError: name 'pathviews' is not defined
[11]:
for prefix in prefixes:
    subset = pathviews.loc[pathviews['Prefix'] == prefix, :]
    subset = subset.loc[~subset['Path'].str.endswith('/index.html'), :]
    subset = subset.sort_values('Views', ascending=False)[:n]
    subset['Path_Clean'] = subset['Path'].str.replace('^/'+prefix+'/', '', regex=True)

    height = (400 // 20) * len(subset)
    extra = f"Top {n}, " if len(subset) == n else ''
    p = figure(y_range=subset['Path_Clean'], height=height, tooltips=[("URL", "@Path"), ("Views", "@Views")],
               title=f"{prefix}: Views by URL ({extra}{data_period})")
    p.hbar(y='Path_Clean', right='Views', source=subset, height=0.75,
           line_color='white')
    p.xaxis.axis_label = 'Total page views'
    show(p)
---------------------------------------------------------------------------
NameError                                 Traceback (most recent call last)
Cell In[11], line 2
      1 for prefix in prefixes:
----> 2     subset = pathviews.loc[pathviews['Prefix'] == prefix, :]
      3     subset = subset.loc[~subset['Path'].str.endswith('/index.html'), :]
      4     subset = subset.sort_values('Views', ascending=False)[:n]

NameError: name 'pathviews' is not defined
[12]:
release_dates = {
    'v0.9.1': '2022-03-29',
    'v0.9.2': '2022-08-19',
    'v0.9.3': '2022-09-15',
    'v0.9.4': '2022-12-20',
    'v0.9.5': '2023-03-18',
    'v0.10.1': '2023-07-05',
    'v0.10.2': '2023-09-21',
    'v0.10.3': '2023-12-20',
    'v0.10.4': '2024-03-19',
    'v0.10.5': '2024-05-06',
    'v0.11.0': '2024-06-21',
    'v0.11.1': '2024-09-26',
    'v0.11.2': '2024-12-16',
    'v0.12.0': '2025-03-19',
}
stable = df.loc[df['Version'] == 'stable', :].copy()
stable['Real_Version'] = '?'
for version, date in release_dates.items():
    stable.loc[stable['Date'] >= date, 'Real_Version'] = version

pivot = stable.pivot_table(values='Views', index='Date', columns='Real_Version', aggfunc='sum')
realigned = pd.DataFrame({
    version: pd.Series(pivot.loc[date:, version].values, index=(pivot.loc[date:, :].index - pd.to_datetime(date)).days)
    for version, date in release_dates.items()
}).cumsum()

tab10 = ['#1f77b4', '#ff7f0e', '#2ca02c', '#d62728', '#9467bd', '#8c564b', '#e377c2', '#7f7f7f', '#bcbd22', '#17becf']

p = figure(height=350, title=f"Cumulative views since release date ({data_period})")
hover_tool = HoverTool(tooltips=[('Days since release', '@x'), ('Cumulative Views', '@y')])
hover_tool.point_policy = 'snap_to_data'
p.add_tools(hover_tool)

for i, column in enumerate(realigned.columns):
    label = column
    if column == 'v0.9.1':
        label = label + ' (partial)'
    p.line(realigned.index, realigned[column], legend_label=label, color=tab10[i])

p.yaxis.axis_label = 'Total page views'
p.xaxis.axis_label = 'Days since release'
p.legend.location = "top_left"
show(p)
---------------------------------------------------------------------------
NameError                                 Traceback (most recent call last)
Cell In[12], line 17
      1 release_dates = {
      2     'v0.9.1': '2022-03-29',
      3     'v0.9.2': '2022-08-19',
   (...)
     15     'v0.12.0': '2025-03-19',
     16 }
---> 17 stable = df.loc[df['Version'] == 'stable', :].copy()
     18 stable['Real_Version'] = '?'
     19 for version, date in release_dates.items():

NameError: name 'df' is not defined

GitHub#

The following plots summarize a subset of all community members that we call “contributors”; it is based on git commit authorship and thus excludes people that file bug reports, contribute to development discussions, and so on.

[13]:
gh = utils.get_github_stars('pvlib/pvlib-python')
---------------------------------------------------------------------------
Exception                                 Traceback (most recent call last)
Cell In[13], line 1
----> 1 gh = utils.get_github_stars('pvlib/pvlib-python')

File ~/work/pv-foss-engagement/pv-foss-engagement/sphinx/source/project/utils.py:83, in get_github_stars(repo)
     82 def get_github_stars(repo):
---> 83     data, N = _fetch_gh_api(repo)
     84     for i in range(2, N+1):
     85         data.extend(_fetch_gh_api(repo, i)[0])

File ~/work/pv-foss-engagement/pv-foss-engagement/sphinx/source/project/utils.py:77, in _fetch_gh_api(repo, page)
     74     N = 1
     76 if isinstance(data, dict) and data.get('status', None) != 200:
---> 77     raise Exception(data)
     79 return data, N

Exception: {'message': 'Bad credentials', 'documentation_url': 'https://docs.github.com/rest', 'status': '401'}
[14]:
p = utils.plot_github_stars_timeseries(gh)
show(p)
---------------------------------------------------------------------------
NameError                                 Traceback (most recent call last)
Cell In[14], line 1
----> 1 p = utils.plot_github_stars_timeseries(gh)
      2 show(p)

NameError: name 'gh' is not defined
[15]:
forks = utils.get_github_forks('pvlib/pvlib-python')
forks = pd.DataFrame({'year': forks.index.year, 'forks': forks})
---------------------------------------------------------------------------
HTTPError                                 Traceback (most recent call last)
Cell In[15], line 1
----> 1 forks = utils.get_github_forks('pvlib/pvlib-python')
      2 forks = pd.DataFrame({'year': forks.index.year, 'forks': forks})

File ~/work/pv-foss-engagement/pv-foss-engagement/sphinx/source/project/utils.py:243, in get_github_forks(repo)
    240 while True:
    241     # print(page)
    242     response = requests.get(url, params=dict(per_page=100, page=page), headers=headers)
--> 243     response.raise_for_status()
    244     json = response.json()
    245     records += json

File /opt/hostedtoolcache/Python/3.11.17/x64/lib/python3.11/site-packages/requests/models.py:1021, in Response.raise_for_status(self)
   1016     http_error_msg = (
   1017         f"{self.status_code} Server Error: {reason} for url: {self.url}"
   1018     )
   1020 if http_error_msg:
-> 1021     raise HTTPError(http_error_msg, response=self)

HTTPError: 401 Client Error: Unauthorized for url: https://api.github.com/repos/pvlib/pvlib-python/forks?per_page=100&page=1
[16]:
p = figure(height=350, tooltips=[("Cumulative forks", "@forks"), ("Year", "@year")],
           title="Cumulative forks")
p.vbar(x='year', top='forks', width=0.75, source=forks)
p.yaxis.axis_label = 'Total forks'
show(p)
---------------------------------------------------------------------------
NameError                                 Traceback (most recent call last)
Cell In[16], line 3
      1 p = figure(height=350, tooltips=[("Cumulative forks", "@forks"), ("Year", "@year")],
      2            title="Cumulative forks")
----> 3 p.vbar(x='year', top='forks', width=0.75, source=forks)
      4 p.yaxis.axis_label = 'Total forks'
      5 show(p)

NameError: name 'forks' is not defined
[17]:
prs = utils.get_github_pull_requests('pvlib/pvlib-python')
prs = pd.DataFrame({'year': prs.index.year, 'prs': prs})
---------------------------------------------------------------------------
HTTPError                                 Traceback (most recent call last)
Cell In[17], line 1
----> 1 prs = utils.get_github_pull_requests('pvlib/pvlib-python')
      2 prs = pd.DataFrame({'year': prs.index.year, 'prs': prs})

File ~/work/pv-foss-engagement/pv-foss-engagement/sphinx/source/project/utils.py:325, in get_github_pull_requests(repo, start, end)
    324 def get_github_pull_requests(repo, start='2010-01-01', end='2030-01-01'):
--> 325     annual_prs = _get_github_pull_requests_recurse(repo, start, end)
    326     return annual_prs.cumsum()

File ~/work/pv-foss-engagement/pv-foss-engagement/sphinx/source/project/utils.py:312, in _get_github_pull_requests_recurse(repo, start, end)
    310 end = pd.to_datetime(end)
    311 try:
--> 312     annual_prs = _get_github_pull_requests(repo, start.strftime("%Y-%m-%d"), end.strftime("%Y-%m-%d"))
    313 except RuntimeError:
    314     # too many results; make smaller requests
    315     diff = end - start

File ~/work/pv-foss-engagement/pv-foss-engagement/sphinx/source/project/utils.py:279, in _get_github_pull_requests(repo, start, end)
    276 while True:
    277     # print(page)
    278     response = requests.get(url, params=dict(q=query, per_page=100, page=page), headers=headers)
--> 279     response.raise_for_status()
    280     json = response.json()
    282     if json['total_count'] >= 1000:
    283         # GH API doesn't let you fetch more than 1000 results

File /opt/hostedtoolcache/Python/3.11.17/x64/lib/python3.11/site-packages/requests/models.py:1021, in Response.raise_for_status(self)
   1016     http_error_msg = (
   1017         f"{self.status_code} Server Error: {reason} for url: {self.url}"
   1018     )
   1020 if http_error_msg:
-> 1021     raise HTTPError(http_error_msg, response=self)

HTTPError: 401 Client Error: Unauthorized for url: https://api.github.com/search/issues?q=repo%3Apvlib%2Fpvlib-python+is%3Apr+merged%3A2010-01-01..2030-01-01&per_page=100&page=1
[18]:
p = figure(height=350, tooltips=[("Cumulative PRs", "@prs"), ("Year", "@year")],
           title="Cumulative PRs")
p.vbar(x='year', top='prs', width=0.75, source=prs)
p.yaxis.axis_label = 'Total PRs'
show(p)
---------------------------------------------------------------------------
NameError                                 Traceback (most recent call last)
Cell In[18], line 3
      1 p = figure(height=350, tooltips=[("Cumulative PRs", "@prs"), ("Year", "@year")],
      2            title="Cumulative PRs")
----> 3 p.vbar(x='year', top='prs', width=0.75, source=prs)
      4 p.yaxis.axis_label = 'Total PRs'
      5 show(p)

NameError: name 'prs' is not defined
[19]:
cumulative_contributors, annual_unique_contributors = utils.get_github_contributor_timeseries("pvlib/pvlib-python")
[20]:
p = utils.plot_github_contributors_timeseries(cumulative_contributors)
show(p)
[21]:
annual_unique_contributors.index = annual_unique_contributors.index.strftime('%Y')
annual_unique_contributors = annual_unique_contributors.reset_index()
annual_unique_contributors.columns = ['year', 'count']

p = figure(x_range=annual_unique_contributors['year'], height=350,
           tooltips=[("Year", "@year"), ("Unique Code Contributors", "@count")],
           title=f"Unique Code Contributors by Year",)
p.vbar(x='year', top='count', width=0.75, source=annual_unique_contributors,
       line_color='white')
p.yaxis.axis_label = 'Unique Code Contributors'
show(p)
[22]:
contributor_images = utils.get_github_contributors('pvlib/pvlib-python')
[23]:
mosaic = utils.make_github_contributors_mosaic(contributor_images)
mosaic
---------------------------------------------------------------------------
TypeError                                 Traceback (most recent call last)
Cell In[23], line 1
----> 1 mosaic = utils.make_github_contributors_mosaic(contributor_images)
      2 mosaic

File ~/work/pv-foss-engagement/pv-foss-engagement/sphinx/source/project/utils.py:139, in make_github_contributors_mosaic(contributor_data, n_wide, n_high)
    137 images = []
    138 for contributor in contributor_data:
--> 139     response = requests.get(contributor['avatar_url'], stream=True)
    140     images.append(Image.open(response.raw))
    142 # randomize order

TypeError: string indices must be integers, not 'str'
[ ]: