Руководство по docstring pandas
О docstring и стандартах
Python docstring — это строка, используемая для документирования модуля, класса, функции или метода Python, чтобы программисты могли понять, что он делает, не читая подробностей реализации.
Кроме того, существует общая практика автоматического генерации онлайн-документации (в формате html) из docstring. Sphinx служит для этой цели.
Следующий пример дает представление о том, как выглядит docstring:
def add(num1, num2):
"""
Add up two integer numbers.
This function simply wraps the `+` operator, and does not
do anything interesting, except for illustrating what is
the docstring of a very simple function.
Parameters
----------
num1 : int
First number to add
num2 : int
Second number to add
Returns
-------
int
The sum of `num1` and `num2`
See Also
--------
subtract : Subtract one integer from another
Examples
--------
>>> add(2, 2)
4
>>> add(25, 0)
25
>>> add(10, -10)
0
"""
return num1 + num2
Существуют определённые стандарты для docstring, чтобы они были легче читаемыми и их можно было экспортировать в другие форматы, такие как html или pdf.
Первые соглашения, которым должен следовать каждый Python docstring, определены в PEP-257.
Так как PEP-257 довольно открыт, существуют и другие стандарты, построенные поверх него. В случае pandas используется соглашение numpy docstring. Данные соглашения объяснены в данном документе:
- Руководство по docstring numpydoc (которое основано на оригинальном Руководстве по документированию NumPy/SciPy)
numpydoc — это расширение Sphinx для поддержки соглашения numpy docstring.
Стандарт использует reStructuredText (reST). reStructuredText — это язык разметки, который позволяет кодировать стили в обычных текстовых файлах. Документацию по reStructuredText можно найти в:
- Вводный курс Sphinx по reStructuredText
- Быстрый справочник по reStructuredText
- Полная спецификация reStructuredText
Pandas предоставляет некоторые вспомогательные средства для совместного использования docstring между связанными классами, см. Обмен docstring.
Остальная часть этого документа подытожит все вышеуказанные руководства и предоставит дополнительные соглашения, специфичные для проекта pandas.
Написание docstring
Общие правила
Docstring должны быть определены с помощью трёх двойных кавычек. Перед и после docstring не должно быть пустых строк. Текст начинается в следующей строке после открывающих кавычек. Закрывающие кавычки находятся в отдельной строке (то есть они не стоят в конце последнего предложения).
В редких случаях в docstring будут использоваться стили reST, такие как жирный шрифт или курсив, но обычно используется встроенный код, который отображается между обратными апострофами. К встроенному коду относятся:
- Имя параметра
- Python-код, модуль, функция, встроенная функция, тип, литерал… (например,
os,list,numpy.abs,datetime.date,True) - Класс pandas (в формате
:class:`pandas.Series`) - Метод pandas (в формате
:meth:`pandas.Series.sum`) - Функция pandas (в формате
:func:`pandas.to_datetime`)
Примечание
Для отображения только последнего компонента связанного класса, метода или функции, префикс должен быть ~. Например, :class:`~pandas.Series` будет ссылаться на pandas.Series, но отображать только последнюю часть, Series, в качестве текста ссылки. Подробности см. в синтаксисе перекрестной ссылки Sphinx.
Хорошо:
def add_values(arr):
"""
Add the values in `arr`.
This is equivalent to Python `sum` of :meth:`pandas.Series.sum`.
Some sections are omitted here for simplicity.
"""
return sum(arr)
Плохо:
def func():
"""Some function.
With several mistakes in the docstring.
It has a blank like after the signature `def func():`.
The text 'Some function' should go in the line after the
opening quotes of the docstring, not in the same line.
There is a blank line between the docstring and the first line
of code `foo = 1`.
The closing quotes should be in the next line, not in this one."""
foo = 1
bar = 2
return foo + bar
Раздел 1: Краткое описание
Краткое описание — это единственное предложение, которое кратко выражает, что делает функция.
Краткое описание должно начинаться с заглавной буквы, заканчиваться точкой и помещаться в одну строку. Оно должно выражать, что делает объект, не предоставляя подробностей. Для функций и методов краткое описание должно начинаться с инфинитива глагола.
Хорошо:
def astype(dtype):
"""
Cast Series type.
This section will provide further details.
"""
pass
Плохо:
def astype(dtype):
"""
Casts Series type.
Verb in third-person of the present simple, should be infinitive.
"""
pass
def astype(dtype):
"""
Method to cast Series type.
Does not start with verb.
"""
pass
def astype(dtype):
"""
Cast Series type
Missing dot at the end.
"""
pass
def astype(dtype):
"""
Cast Series type from its current type to the new type defined in
the parameter dtype.
Summary is too verbose and doesn't fit in a single line.
"""
pass
Раздел 2: Подробное описание
Подробное описание предоставляет подробности о том, что делает функция. Оно не должно вдаваться в подробности параметров или обсуждать заметки по реализации, которые размещаются в других разделах.
Между кратким и подробным описанием оставляется пустая строка. И каждое предложение в подробном описании заканчивается точкой.
Подробное описание должно предоставлять подробности о том, почему функция полезна и какие у неё варианты использования, если она не слишком общая.
def unstack():
"""
Pivot a row index to columns.
When using a MultiIndex, a level can be pivoted so each value in
the index becomes a column. This is especially useful when a subindex
is repeated for the main index, and data is easier to visualize as a
pivot table.
The index level will be automatically removed from the index when added
as columns.
"""
pass
Раздел 3: Параметры
Подробности параметров будут добавлены в этом разделе. Этот раздел имеет заголовок «Параметры», после которого идёт строка с дефисами под каждой буквой слова «Параметры». Перед заголовком оставляется пустая строка, но не после него и не между строкой со словом «Параметры» и строкой с дефисами.
После заголовка каждый параметр в сигнатуре должен быть задокументирован, включая *args и **kwargs, но не self.
Параметры определяются по имени, за которым следует пробел, двоеточие, ещё один пробел и тип (или типы). Обратите внимание, что пробел между именем и двоеточием важен. Типы не определены для *args и **kwargs, но должны быть определены для всех остальных параметров. После определения параметра требуется строка с описанием параметра, которая отступает и может содержать несколько строк. Описание должно начинаться с заглавной буквы и заканчиваться точкой.
Для ключевых аргументов с заданным значением по умолчанию значение по умолчанию будет указано после запятой в конце типа. Точная форма типа в этом случае будет «int, значение по умолчанию 0». В некоторых случаях может быть полезно объяснить, что означает аргумент по умолчанию, что можно добавить после запятой «int, значение по умолчанию -1, означающий все процессоры».
В тех случаях, когда значение по умолчанию None, означающее, что значение не будет использоваться. Вместо «str, значение по умолчанию None» предпочтительнее писать «str, необязательный». Когда None — используемое значение, мы сохраним форму «str, значение по умолчанию None». Например, в df.to_csv(compression=None), None — не используемое значение, но означает, что сжатие является необязательным и не используется, если не указано. В этом случае мы будем использовать str, optional. Только в случаях, таких как func(value=None) и None используется так же, как 0 или foo, тогда мы укажем «str, int или None, значение по умолчанию None».
Хорошо:
class Series:
def plot(self, kind, color='blue', **kwargs):
"""
Generate a plot.
Render the data in the Series as a matplotlib plot of the
specified kind.
Parameters
----------
kind : str
Kind of matplotlib plot.
color : str, default 'blue'
Color name or rgb code.
**kwargs
These parameters will be passed to the matplotlib plotting
function.
"""
pass
Плохо:
class Series:
def plot(self, kind, **kwargs):
"""
Generate a plot.
Render the data in the Series as a matplotlib plot of the
specified kind.
Note the blank line between the parameters title and the first
parameter. Also, note that after the name of the parameter `kind`
and before the colon, a space is missing.
Also, note that the parameter descriptions do not start with a
capital letter, and do not finish with a dot.
Finally, the `**kwargs` parameter is missing.
Parameters
----------
kind: str
kind of matplotlib plot
"""
pass
Типы параметров
При указании типов параметров можно использовать встроенные типы данных Python (предпочтительнее использовать тип Python, а не более громоздкие строка, целое число, логическое значение и т. д.):
- int
- float
- str
- bool
Для сложных типов определите подтипы. Для dict и tuple, поскольку присутствует более одного типа, мы используем скобки для улучшения читаемости типа (фигурные скобки для dict и обычные скобки для tuple):
- список целых чисел
- словарь {строка : целое число}
- кортеж (строка, целое число, целое число)
- кортеж (строка,)
- множество строк
В случае, когда разрешено только множество значений, перечислите их в фигурных скобках, разделенных запятыми (за которыми следует пробел). Если значения упорядочены и имеют порядок, перечислите их в этом порядке. В противном случае перечислите значение по умолчанию первым, если оно есть:
- {0, 10, 25}
- {‘простой’, ‘расширенный’}
- {‘низкий’, ‘средний’, ‘высокий’}
- {‘кот’, ‘собака’, ‘птица’}
Если тип определён в Python-модуле, необходимо указать модуль:
- datetime.date
- datetime.datetime
- decimal.Decimal
Если тип находится в пакете, также нужно указать модуль:
- numpy.ndarray
- scipy.sparse.coo_matrix
Если тип является типом pandas, также укажите pandas, за исключением Series и DataFrame:
- Series
- DataFrame
- pandas.Index
- pandas.Categorical
- pandas.SparseArray
Если точный тип не важен, но должен быть совместим с массивом numpy, можно указать array-like. Если принимается любой тип, который можно итерировать, можно использовать iterable:
- array-like
- iterable
Если принимается более одного типа, разделите их запятыми, за исключением двух последних типов, которые нужно разделить словом «или»:
- целое число или число с плавающей точкой
- число с плавающей точкой, decimal.Decimal или None
- строка или список строк
Если None — одно из допустимых значений, оно всегда должно стоять последним в списке.
Для оси соглашение состоит в использовании чего-то вроде:
- axis : {0 или ‘индекс’, 1 или ‘колонки’, None}, значение по умолчанию None
Раздел 4: Возвращает или Выдает
Если метод возвращает значение, оно будет задокументировано в этом разделе. Также, если метод выдает свой результат.
Заголовок раздела будет определён так же, как и «Параметры». С именами «Возвращает» или «Выдает», за которыми следует строка с таким же количеством дефисов, как букв в предшествующем слове.
Документация возвращаемого значения также аналогична документации параметров. Но в этом случае не будет предоставлено имя, если метод не возвращает или не выдает более одного значения (кортеж значений).
Типы для «Возвращает» и «Выдает» такие же, как для «Параметров». Кроме того, описание должно заканчиваться точкой.
Например, с одним значением:
def sample():
"""
Generate and return a random number.
The value is sampled from a continuous uniform distribution between
0 and 1.
Returns
-------
float
Random number generated.
"""
return np.random.random()
С несколькими значениями:
import string
def random_letters():
"""
Generate and return a sequence of random letters.
The length of the returned string is also random, and is also
returned.
Returns
-------
length : int
Length of the returned string.
letters : str
String of random letters.
"""
length = np.random.randint(1, 10)
letters = ''.join(np.random.choice(string.ascii_lowercase)
for i in range(length))
return length, letters
Если метод выдает значения:
def sample_values():
"""
Generate an infinite sequence of random numbers.
The values are sampled from a continuous uniform distribution between
0 and 1.
Yields
------
float
Random number generated.
"""
while True:
yield np.random.random()
Раздел 5: См. также
Этот раздел используется, чтобы пользователи узнали о связанной функциональности pandas. В редких случаях, если не могут быть найдены связанные методы или функции, этот раздел можно пропустить.
Ярким примером являются методы head() и tail(). Поскольку tail() выполняет эквивалентную операцию, что и head(), но в конце Series или DataFrame, а не в начале, полезно проинформировать об этом пользователей.
Для того чтобы понять, что может считаться связанным, вот некоторые примеры:
-
locиiloc, так как они выполняют одну и ту же задачу, но в одном случае предоставляют индексы, а в другом — позиции -
maxиmin, так как они делают обратное -
iterrows,itertuplesиitems, так как легко может произойти ошибка, когда пользователь, ищущий метод для итерации по столбцам, попадает на метод для итерации по строкам, и наоборот -
fillnaиdropna, так как оба метода используются для обработки пропущенных значений -
read_csvиto_csv, так как они являются взаимодополняющими -
mergeиjoin, так как один является обобщением другого -
astypeиpandas.to_datetime, так как пользователи могут читать документацию поastype, чтобы узнать, как преобразовать значение в дату, а сделать это можно с помощьюpandas.to_datetime -
whereсвязан сnumpy.where, так как его функциональность основана на нём
При определении связанных элементов следует в первую очередь руководствоваться здравым смыслом и думать о том, что может быть полезно для пользователей, читающих документацию, особенно для менее опытных.
При ссылках на другие библиотеки (в основном numpy) сначала используйте имя модуля (а не псевдоним, как np). Если функция находится в модуле, который не является основным, например, scipy.sparse, указывайте полный путь к модулю (например, scipy.sparse.coo_matrix).
Этот раздел, как и предыдущий, также имеет заголовок «См. также» (обратите внимание на заглавные буквы S и A). За ним следует строка с дефисами, а перед ней — пустая строка.
После заголовка будет добавлена строка для каждого связанного метода или функции, за которой следует пробел, двоеточие, ещё один пробел и краткое описание, иллюстрирующее, что делает этот метод или функция, почему он важен в данном контексте и каковы ключевые различия между документированной функцией и функцией, на которую делается ссылка. Описание также должно заканчиваться точкой.
Обратите внимание, что в разделах «Возвращает» и «Выдаёт» описание находится в следующей строке после типа. Но в этом разделе оно расположено в той же строке с двоеточием между ними. Если описание не помещается в одну строку, оно может продолжаться в следующих, но должно быть отступать в них.
Например:
class Series:
def head(self):
"""
Return the first 5 elements of the Series.
This function is mainly useful to preview the values of the
Series without displaying the whole of it.
Returns
-------
Series
Subset of the original series with the 5 first values.
See Also
--------
Series.tail : Return the last 5 elements of the Series.
Series.iloc : Return a slice of the elements in the Series,
which can also be used to return the first or last n.
"""
return self.iloc[:5]
Раздел 6: Примечания
Этот необязательный раздел используется для примечаний об реализации алгоритма. Или для документирования технических аспектов поведения функции.
Вы можете пропустить его, если вы не знакомы с реализацией алгоритма или обнаружили какое-то неинтуитивное поведение при написании примеров для функции.
Этот раздел следует тому же формату, что и раздел расширенного описания.
Раздел 7: Примеры
Это один из самых важных разделов документации, даже если он находится на последнем месте. Как часто бывает, люди лучше понимают концепции на примерах, чем на точных объяснениях.
Примеры в документации, помимо иллюстрации использования функции или метода, должны быть корректным кодом Python, который детерминированно возвращает представленный результат и который пользователи могут скопировать и запустить.
Они представляются как сеанс в терминале Python. >>> используется для представления кода. … используется для продолжения кода с предыдущей строки. Результат отображается непосредственно после последней строки кода, генерирующей результат (пробелов между ними нет). Комментарии, описывающие примеры, могут быть добавлены с пустыми строками перед и после них.
Способ представления примеров следующий:
- Импортировать необходимые библиотеки (кроме
numpyиpandas) - Создать данные, необходимые для примера
- Показать очень простой пример, который даёт представление о наиболее часто используемом случае
- Добавить примеры с пояснениями, иллюстрирующими, как параметры могут использоваться для расширенной функциональности
Простой пример может быть таким:
class Series:
def head(self, n=5):
"""
Return the first elements of the Series.
This function is mainly useful to preview the values of the
Series without displaying the whole of it.
Parameters
----------
n : int
Number of values to return.
Return
------
pandas.Series
Subset of the original series with the n first values.
See Also
--------
tail : Return the last n elements of the Series.
Examples
--------
>>> s = pd.Series(['Ant', 'Bear', 'Cow', 'Dog', 'Falcon',
... 'Lion', 'Monkey', 'Rabbit', 'Zebra'])
>>> s.head()
0 Ant
1 Bear
2 Cow
3 Dog
4 Falcon
dtype: object
With the `n` parameter, we can change the number of returned rows:
>>> s.head(n=3)
0 Ant
1 Bear
2 Cow
dtype: object
"""
return self.iloc[:n]
Примеры должны быть максимально лаконичными. В тех случаях, когда сложность функции требует длинных примеров, рекомендуется использовать блоки с заголовками в жирном шрифте. Используйте двойные звёзды ** для выделения текста жирным шрифтом, как в **this example**.
Рекомендации по примерам
Предполагается, что код в примерах всегда начинается с этих двух строк, которые не отображаются:
import numpy as np import pandas as pd
Любой другой используемый в примерах модуль должен быть явно импортирован, по одной строке (как рекомендуется в PEP 8#imports) и без псевдонимов. Избегайте чрезмерных импортов, но если это необходимо, импорты из стандартной библиотеки идут первыми, за ними — библиотеки сторонних разработчиков (например, matplotlib).
При иллюстрации примера с одним Series используйте имя s, а если иллюстрируете с одним DataFrame используйте имя df. Для индексов предпочтительным именем является idx . Если используется набор однородных Series или DataFrame, назовите их s1, s2, s3… или df1, df2, df3… Если данные не однородны и требуется более одной структуры, назовите их осмысленно, например, df_main и df_to_join.
Данные, используемые в примере, должны быть максимально компактными. Рекомендуемое количество строк — около 4, но сделайте его числом, имеющим смысл для конкретного примера. Например, в методе head это требует быть больше 5, чтобы показать пример с параметрами по умолчанию. Если выполняете mean, можно использовать что-то вроде [1, 2, 3], чтобы легко увидеть, что возвращаемое значение — среднее.
Для более сложных примеров (например, группировки) избегайте использования данных без интерпретации, таких как матрица случайных чисел со столбцами A, B, C, D... Вместо этого используйте содержательный пример, который упрощает понимание концепции. Если пример не требует этого, используйте названия животных, чтобы примеры были согласованы, и числовые свойства этих животных.
При вызове метода предпочтительны именованные аргументы head(n=3) по сравнению с позиционными аргументами head(3).
Хорошо:
class Series:
def mean(self):
"""
Compute the mean of the input.
Examples
--------
>>> s = pd.Series([1, 2, 3])
>>> s.mean()
2
"""
pass
def fillna(self, value):
"""
Replace missing values by `value`.
Examples
--------
>>> s = pd.Series([1, np.nan, 3])
>>> s.fillna(0)
[1, 0, 3]
"""
pass
def groupby_mean(self):
"""
Group by index and return mean.
Examples
--------
>>> s = pd.Series([380., 370., 24., 26],
... name='max_speed',
... index=['falcon', 'falcon', 'parrot', 'parrot'])
>>> s.groupby_mean()
index
falcon 375.0
parrot 25.0
Name: max_speed, dtype: float64
"""
pass
def contains(self, pattern, case_sensitive=True, na=numpy.nan):
"""
Return whether each value contains `pattern`.
In this case, we are illustrating how to use sections, even
if the example is simple enough and does not require them.
Examples
--------
>>> s = pd.Series('Antelope', 'Lion', 'Zebra', np.nan)
>>> s.contains(pattern='a')
0 False
1 False
2 True
3 NaN
dtype: bool
**Case sensitivity**
With `case_sensitive` set to `False` we can match `a` with both
`a` and `A`:
>>> s.contains(pattern='a', case_sensitive=False)
0 True
1 False
2 True
3 NaN
dtype: bool
**Missing values**
We can fill missing values in the output using the `na` parameter:
>>> s.contains(pattern='a', na=False)
0 False
1 False
2 True
3 False
dtype: bool
"""
pass
Плохо:
def method(foo=None, bar=None):
"""
A sample DataFrame method.
Do not import numpy and pandas.
Try to use meaningful data, when it makes the example easier
to understand.
Try to avoid positional arguments like in `df.method(1)`. They
can be all right if previously defined with a meaningful name,
like in `present_value(interest_rate)`, but avoid them otherwise.
When presenting the behavior with different parameters, do not place
all the calls one next to the other. Instead, add a short sentence
explaining what the example shows.
Examples
--------
>>> import numpy as np
>>> import pandas as pd
>>> df = pd.DataFrame(np.random.randn(3, 3),
... columns=('a', 'b', 'c'))
>>> df.method(1)
21
>>> df.method(bar=14)
123
"""
pass
Советы по прохождению тестов doctest
Прохождение тестов doctest в скрипте проверки иногда может быть сложным. Вот несколько важных моментов:
- Импортируйте все необходимые библиотеки (кроме pandas и numpy, которые уже импортированы как
import pandas as pdиimport numpy as np) и определите все переменные, которые вы используете в примере. -
Постарайтесь избегать использования случайных данных. Однако случайные данные могут быть приемлемы в некоторых случаях, например, если документируемая функция имеет дело с распределениями вероятностей или если необходимо слишком много данных, чтобы сделать результат функции осмысленным, и создание данных вручную слишком громоздко. В этих случаях всегда используйте фиксированное случайное семя, чтобы сделать генерируемые примеры предсказуемыми. Пример:
>>> np.random.seed(42) >>> df = pd.DataFrame({'normal': np.random.normal(100, 5, 20)}) -
Если у вас есть фрагмент кода, занимающий несколько строк, вам необходимо использовать «…» в продолженных строках:
>>> df = pd.DataFrame([[1, 2, 3], [4, 5, 6]], index=['a', 'b', 'c'], ... columns=['A', 'B'])
-
Если вы хотите показать случай, когда возникает исключение, вы можете сделать так:
>>> pd.to_datetime(["712-01-01"]) Traceback (most recent call last): OutOfBoundsDatetime: Out of bounds nanosecond timestamp: 712-01-01 00:00:00
Важно включить «Traceback (most recent call last):», но для фактической ошибки достаточно имени ошибки.
-
Если небольшая часть результата может изменяться (например, хеш в представлении объекта), вы можете использовать
...для обозначения этой части.Если вы хотите показать, что
s.plot()возвращает объект matplotlib AxesSubplot, это не пройдёт doctest>>> s.plot() <matplotlib.axes._subplots.AxesSubplot at 0x7efd0c0b0690>
Однако вы можете сделать так (обратите внимание на комментарий, который нужно добавить)
>>> s.plot() <matplotlib.axes._subplots.AxesSubplot at ...>
Диаграммы в примерах
В pandas существуют некоторые методы, возвращающие диаграммы. Для отображения диаграмм, сгенерированных примерами в документации, существует директива .. plot::.
Чтобы использовать её, поместите следующий код после заголовка «Примеры», как показано ниже. Диаграмма будет сгенерирована автоматически при построении документации.
class Series:
def plot(self):
"""
Generate a plot with the `Series` data.
Examples
--------
.. plot::
:context: close-figs
>>> s = pd.Series([1, 2, 3])
>>> s.plot()
"""
pass
Обмен документацией
Каждая общая документация будет иметь шаблон базы с переменными, такими как %(klass)s. Переменные заполняются позже с помощью декоратора Substitution . Наконец, документацию можно дополнить с помощью декоратора Appender.
В этом примере мы создадим родительскую документацию стандартным способом (это как pandas.core.generic.NDFrame. Затем у нас будет два потомка (как pandas.core.series.Series и pandas.core.frame.DataFrame). Мы заменим имена классов потомков в этой документации.
class Parent:
def my_function(self):
"""Apply my function to %(klass)s."""
...
class ChildA(Parent):
@Substitution(klass="ChildA")
@Appender(Parent.my_function.__doc__)
def my_function(self):
...
class ChildB(Parent):
@Substitution(klass="ChildB")
@Appender(Parent.my_function.__doc__)
def my_function(self):
...
Полученные документации:
>>> print(Parent.my_function.__doc__) Apply my function to %(klass)s. >>> print(ChildA.my_function.__doc__) Apply my function to ChildA. >>> print(ChildB.my_function.__doc__) Apply my function to ChildB.
Обратите внимание на два момента:
- Мы «дополняем» родительскую документацию документацией потомков, которые изначально пустые.
- Декораторы Python применяются с внешнего внутрь. Таким образом, порядок — Дополнение, затем Подстановка, даже если Подстановка указана первой в файле.
Наши файлы часто содержат модульную _shared_doc_kwargs с общими значениями подстановки (например, klass, axes, и т. д.).
Вы можете заменить и добавить в одном шаге, используя что-то вроде
@Appender(template % _shared_doc_kwargs)
def my_function(self):
...
где template может быть получено из модульной _shared_docs словаря, сопоставляющего имена функций с документацией. По возможности мы предпочитаем использовать Appender и Substitution, так как процесс написания документации становится более привычным.
См. pandas.core.generic.NDFrame.fillna для примера шаблона и pandas.core.series.Series.fillna и pandas.core.generic.frame.fillna для заполненных версий.
© 2008–2012, AQR Capital Management, LLC, Lambda Foundry, Inc. and PyData Development Team
Licensed under the 3-clause BSD License.
https://pandas.pydata.org/pandas-docs/version/0.25.0/development/contributing_docstring.html