pandas 文件字串指南#

關於文件字串和標準#

Python 文件字串是用來記錄 Python 模組、類別、函式或方法的字串,讓程式設計師可以了解其功能,而無需閱讀實作的詳細資料。

此外,從文件字串自動產生線上(html)文件也是常見的做法。Sphinx 就提供這項功能。

以下範例說明文件字串的樣貌

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
    the docstring of a very simple function looks like.

    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

文件字串有一些標準,讓文件字串更容易閱讀,並能輕鬆匯出成其他格式,例如 html 或 pdf。

每個 Python 文件字串都應遵循的第一個慣例定義在 PEP-257 中。

由於 PEP-257 涵蓋範圍很廣,因此也有其他更具體的標準。在 pandas 的情況下,遵循 NumPy 文件字串慣例。這些慣例說明在這份文件中

numpydoc 是 Sphinx 的擴充功能,用來支援 NumPy 文件字串慣例。

該標準使用 reStructuredText (reST)。reStructuredText 是一種標記語言,允許在純文字檔中編碼樣式。reStructuredText 的文件可以在以下位置找到

pandas 有些輔助工具可以讓相關類別共用文件字串,請參閱 共用文件字串

本文件其餘部分將摘要上述所有準則,並提供特定於 pandas 專案的其他慣例。

撰寫文件字串#

一般規則#

文件字串必須定義為三個雙引號。文件字串前後不應留空白行。文字從開頭引號後的下一行開始。結尾引號有自己的行(表示它們不在最後一句的結尾)。

在罕見的情況下,reST 樣式(如粗體文字或斜體字)將用於文件字串中,但通常會有內嵌程式碼,它會顯示在反引號之間。下列被視為內嵌程式碼

  • 參數名稱

  • Python 程式碼、模組、函式、內建、類型、文字…(例如 oslistnumpy.absdatetime.dateTrue

  • 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,表示所有 CPU」。

在預設值為 None 的情況下,表示不會使用該值。建議改寫為 "str, optional",而非 "str, default None"。當 None 是正在使用的值時,我們會保留「str,預設值為 None」的形式。例如,在 df.to_csv(compression=None) 中,None 不是正在使用的值,而是表示壓縮是選用的,如果未提供,則不使用壓縮。在這種情況下,我們將使用 "str, optional"。只有在 func(value=None)None 的使用方式與 0foo 相同的情況下,我們才會指定「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

對於複雜類型,請定義子類型。對於 dicttuple,由於存在多種類型,我們使用括號來協助讀取類型(dict 使用大括號,tuple 使用一般括號)

  • int 清單

  • 字串:int 字典

  • (字串、int、int) 元組

  • (字串) 元組

  • 字串集合

如果只允許一組值,請將它們列在花括號中,並以逗號分隔(後跟空格)。如果值是序數且有順序,請按此順序列出。否則,如果有的話,請先列出預設值

  • {0, 10, 25}

  • {‘simple’, ‘advanced’}

  • {‘low’, ‘medium’, ‘high’}

  • {‘cat’, ‘dog’, ‘bird’}

如果類型在 Python 模組中定義,則必須指定模組

  • datetime.date

  • datetime.datetime

  • decimal.Decimal

如果類型在套件中,則也必須指定模組

  • numpy.ndarray

  • scipy.sparse.coo_matrix

如果類型是 pandas 類型,除了 Series 和 DataFrame 之外,還要指定 pandas

  • Series

  • DataFrame

  • pandas.Index

  • pandas.Categorical

  • pandas.arrays.SparseArray

如果確切類型不相關,但必須與 NumPy 陣列相容,則可以指定類陣列。如果可以接受任何可迭代的類型,則可以使用可迭代

  • 類陣列

  • 可迭代

如果接受多種類型,請用逗號分隔,最後兩個類型除外,這兩個類型需要用「或」字詞分隔

  • int 或 float

  • float、decimal.Decimal 或 None

  • 字串或字串清單

如果 None 是可接受的值之一,它總是需要在清單中最後。

對於軸,慣例是使用類似以下的內容

  • 軸:{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(),但出現在 SeriesDataFrame 的結尾,而非開頭,因此最好讓使用者知道這一點。

以下提供一些範例,讓您了解哪些內容可以視為相關內容

  • lociloc,因為它們執行相同的動作,但一個提供索引,另一個提供位置

  • maxmin,因為它們執行相反的動作

  • iterrowsitertuplesitems,因為使用者尋找迭代欄位的方法時,很容易會找到迭代列的方法,反之亦然

  • fillnadropna,因為這兩個方法都用於處理遺失值

  • read_csvto_csv,因為它們是互補的

  • mergejoin,因為其中一個是另一個的概括

  • astypepandas.to_datetime,因為使用者可能會閱讀 astype 的文件,以了解如何轉換為日期,而這樣做的方式是使用 pandas.to_datetime

  • wherenumpy.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 終端的會話方式呈現。 >>> 用於呈現程式碼。 ... 用於延續前一行的程式碼。輸出會在產生輸出的最後一行程式碼之後立即呈現(中間沒有空白行)。說明範例的註解可以在範例前後加上空白行。

呈現範例的方式如下

  1. 匯入必要的函式庫(numpypandas 除外)

  2. 建立範例所需資料

  3. 顯示一個非常基本的範例,說明最常見的使用案例

  4. 加入說明如何使用參數以擴充功能的範例

一個簡單的範例可以是

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 all 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
        --------
        >>> ser = pd.Series(['Ant', 'Bear', 'Cow', 'Dog', 'Falcon',
        ...                'Lion', 'Monkey', 'Rabbit', 'Zebra'])
        >>> ser.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:

        >>> ser.head(n=3)
        0   Ant
        1   Bear
        2   Cow
        dtype: object
        """
        return self.iloc[:n]

範例應盡可能簡潔。如果函數的複雜度需要長篇的範例,建議使用粗體標題的區塊。使用兩個星號 ** 讓文字變粗體,例如 **這個** 範例**

範例的慣例#

範例中的程式碼假設總是從這兩行開始,但不會顯示

import numpy as np
import pandas as pd

範例中使用的任何其他模組都必須明確匯入,每行一個(如 PEP 8#imports 中建議),並避免使用別名。避免過度匯入,但如果需要,則標準函式庫的匯入優先,其次是第三方函式庫(例如 matplotlib)。

當使用單一 Series 來說明範例時,請使用名稱 ser,如果使用單一 DataFrame 來說明,則使用名稱 df。對於索引,idx 是建議使用的名稱。如果使用一組同質的 SeriesDataFrame,請將它們命名為 ser1ser2ser3… 或 df1df2df3… 如果資料不同質,且需要多個結構,請使用有意義的名稱命名它們,例如 df_maindf_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
        --------
        >>> ser = pd.Series([1, 2, 3])
        >>> ser.mean()
        2
        """
        pass


    def fillna(self, value):
        """
        Replace missing values by ``value``.

        Examples
        --------
        >>> ser = pd.Series([1, np.nan, 3])
        >>> ser.fillna(0)
        [1, 0, 3]
        """
        pass

    def groupby_mean(self):
        """
        Group by index and return mean.

        Examples
        --------
        >>> ser = pd.Series([380., 370., 24., 26],
        ...               name='max_speed',
        ...               index=['falcon', 'falcon', 'parrot', 'parrot'])
        >>> ser.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
        --------
        >>> ser = pd.Series('Antelope', 'Lion', 'Zebra', np.nan)
        >>> ser.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:

        >>> ser.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 pdimport 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

            >>> ser = pd.Series([1, 2, 3])
            >>> ser.plot()
        """
        pass

共用文件字串#

pandas 有一個系統可以讓類別共用文件字串,但會有些微差異。這有助於我們讓文件字串保持一致,同時讓使用者閱讀時一目了然。但寫作時會稍微複雜一些。

每個共用的文件字串都會有一個包含變數的基本範本,例如 {klass}。稍後會使用 doc 裝飾器填入變數。最後,文件字串也可以使用 doc 裝飾器附加。

在此範例中,我們會正常建立一個父文件字串(類似 pandas.core.generic.NDFrame)。然後我們會有兩個子文件字串(類似 pandas.core.series.Seriespandas.core.frame.DataFrame)。我們會在這個文件字串中替換類別名稱。

class Parent:
    @doc(klass="Parent")
    def my_function(self):
        """Apply my function to {klass}."""
        ...


class ChildA(Parent):
    @doc(Parent.my_function, klass="ChildA")
    def my_function(self):
        ...


class ChildB(Parent):
    @doc(Parent.my_function, klass="ChildB")
    def my_function(self):
        ...

產生的文件字串為

>>> print(Parent.my_function.__doc__)
Apply my function to Parent.
>>> print(ChildA.my_function.__doc__)
Apply my function to ChildA.
>>> print(ChildB.my_function.__doc__)
Apply my function to ChildB.

注意

  1. 我們將父文件字串「附加」到子文件字串,而子文件字串最初是空的。

我們的檔案通常會包含一個模組層級的 _shared_doc_kwargs,其中包含一些常見的替換值(例如 klassaxes 等)。

你可以使用類似以下內容一次替換和附加

@doc(template, **_shared_doc_kwargs)
def my_function(self):
    ...

其中 template 可能來自模組層級的 _shared_docs 字典,將函式名稱對應到文件字串。在可能的情況下,我們比較喜歡使用 doc,因為文件字串寫作流程會稍微更接近一般。

請參閱 pandas.core.generic.NDFrame.fillna 以取得範本範例,以及 pandas.core.series.Series.fillnapandas.core.generic.frame.fillna 以取得已填入的版本。