pandas DataFrame：`data[...]`では、インデックスは列参照、スライスは行参照を表す


pandasのDataFrameを初めて使った際に、ハマったことをまとめます。

# 環境
* Python 3.6
* pandas 0.23.0
* IPython 6.4.0


# やりたいこと

以下のような、pandasのDataFrameオブジェクトがあります。

```python
area = pd.Series({'California': 423967, 'Texas': 695662,
                  'New York': 141297, 'Florida': 170312,
                  'Illinois': 149995})
pop = pd.Series({'California': 38332521, 'Texas': 26448193,
                 'New York': 19651127, 'Florida': 19552860,
                 'Illinois': 12882135})
data = pd.DataFrame({'area':area, 'pop':pop})

```


```plain:IPython_Console
In [1]: data
Out[1]:
              area       pop
California  423967  38332521
Texas       695662  26448193
New York    141297  19651127
Florida     170312  19552860
Illinois    149995  12882135
```

このDataFrameオブジェクトに対して、行単位でデータを取得したいです。


# ハマったこと

`data`に対してスライスでアクセスすると、行単位のデータを取得できます。

```plain:IPython_Console
In [2]: data[1:3]
Out[2]:
            area       pop
Texas     695662  26448193
New York  141297  19651127
```

しかし、`data`に対してインデックスでアクセスすると、`KeyError`が発生しました。

```
In [2]: data[1]
---------------------------------------------------------------------------
KeyError                                  Traceback (most recent call last)
~\Anaconda3_5.2.0\lib\site-packages\pandas\core\indexes\base.py in get_loc(self, key, method, tolerance)
   3062             try:
-> 3063                 return self._engine.get_loc(key)
   3064             except KeyError:

pandas\_libs\index.pyx in pandas._libs.index.IndexEngine.get_loc()

pandas\_libs\index.pyx in pandas._libs.index.IndexEngine.get_loc()

pandas\_libs\hashtable_class_helper.pxi in pandas._libs.hashtable.PyObjectHashTable.get_item()

pandas\_libs\hashtable_class_helper.pxi in pandas._libs.hashtable.PyObjectHashTable.get_item()

KeyError: 1
```

私は、「DataFrameを行要素のlistとして扱える」と期待していたので、上記の現象が理解できませんでした。

# 原因
pandasのDataFrameは、「インデックスは列を参照、スライスは行を参照」するからです。

「Python データサイエンス　ハンドブック」の英語版には、以下の文が書かれていました。

> First, while indexing refers to columns, slicing refers to rows:

[Data Indexing and Selection - Python Data Science Handbook](https://render.githubusercontent.com/view/ipynb?commit=599aa0fe3f882c0001670e676e5a8d43b92c35fc&enc_url=68747470733a2f2f7261772e67697468756275736572636f6e74656e742e636f6d2f6a616b657664702f507974686f6e44617461536369656e636548616e64626f6f6b2f353939616130666533663838326330303031363730653637366535613864343362393263333566632f6e6f7465626f6f6b732f30332e30322d446174612d496e646578696e672d616e642d53656c656374696f6e2e6970796e62&nwo=jakevdp%2FPythonDataScienceHandbook&path=notebooks%2F03.02-Data-Indexing-and-Selection.ipynb&repository_id=65388917&repository_type=Repository#Additional-indexing-conventions) 引用

以下のようなアクセス方法もあります。

```plain:IPython_Console
In [3]: data['Florida':'Illinois']
Out[3]:
            area       pop
Florida   170312  19552860
Illinois  149995  12882135
```


## pandas DataFrameのソースコード

`data[...]`でアクセスしたときに呼び出される、`__getitem__`メソッドの中身です。


```python:frame.py
class DataFrame(NDFrame):
# ....

    def __getitem__(self, key):
        key = com._apply_if_callable(key, self)

        # shortcut if we are an actual column
        is_mi_columns = isinstance(self.columns, MultiIndex)
        try:
            if key in self.columns and not is_mi_columns:
                return self._getitem_column(key)
        except:
            pass

        # see if we can slice the rows
        indexer = convert_to_index_sliceable(self, key)
        if indexer is not None:
            return self._getitem_slice(indexer)

        if isinstance(key, (Series, np.ndarray, Index, list)):
            # either boolean or fancy integer index
            return self._getitem_array(key)
        elif isinstance(key, DataFrame):
            return self._getitem_frame(key)
        elif is_mi_columns:
            return self._getitem_multilevel(key)
        else:
            return self._getitem_column(key)
```            

`data[1]`でアクセスしたときは、最後の`else`ブロックに入り、`self._getitem_column(key)`を実行します。
そして`1`という列はないので、`KeyError`が発生します。



# 解決

`data.iloc`を使います。

>Purely integer-location based indexing for selection by position.

https://pandas.pydata.org/pandas-docs/version/0.21/generated/pandas.DataFrame.iloc.html 引用


```plain:IPython_Console
In [4]: data.iloc[1]
Out[4]:
area      695662
pop     26448193
Name: Texas, dtype: int64
```


[Indexers: loc, iloc, and ix](https://render.githubusercontent.com/view/ipynb?commit=599aa0fe3f882c0001670e676e5a8d43b92c35fc&enc_url=68747470733a2f2f7261772e67697468756275736572636f6e74656e742e636f6d2f6a616b657664702f507974686f6e44617461536369656e636548616e64626f6f6b2f353939616130666533663838326330303031363730653637366535613864343362393263333566632f6e6f7465626f6f6b732f30332e30322d446174612d496e646578696e672d616e642d53656c656374696f6e2e6970796e62&nwo=jakevdp%2FPythonDataScienceHandbook&path=notebooks%2F03.02-Data-Indexing-and-Selection.ipynb&repository_id=65388917&repository_type=Repository#Indexers:-loc,-iloc,-and-ix) 参考


# 補足


## スライスに列名のキーを使う場合

```python
dict_list = [dict(a=1,b=2,c=3,d=4), dict(a=11,b=12,c=13,d=14)]
data2 = pd.DataFrame(dict_list)
```

列`b`から列`d`までを抽出したい場合は、`data2.loc[:,"b":"d"]`でアクセスします。

```IPython_Console
In [21]: data2
Out[21]:
    a   b   c   d
0   1   2   3   4
1  11  12  13  14

In [24]: data2.loc[:, "b":"d"]
Out[24]:
    b   c   d
0   2   3   4
1  12  13  14

```

## 複数の列を抽出する場合
`data[...]`に、抽出したい列名の`list`を渡せばよいです。


```IPython_Console                                    
In [26]: data2[["a","c"]]           
Out[26]:                            
    a   c                           
0   1   3                           
1  11  13                           
```

ちなみに、インデックスにlistを渡せばDataFrameが、文字列を渡せばSeriesが返ります。

```IPython_Console                                        
In [30]: type(data2[["a"]])
Out[30]: pandas.core.frame.DataFrame

In [31]: type(data2["a"])
Out[31]: pandas.core.series.Series
```



# 参考

* この記事のコードは、以下のサイトのコードを参考にしました。
https://github.com/jakevdp/PythonDataScienceHandbook/blob/master/notebooks/03.02-Data-Indexing-and-Selection.ipynb