Series, DataFrame, and Index.
import numpy as np
import pandas as pdSeries is a one-dimensional array of indexed data.
It can be created from a list or array as follows:
data = pd.Series([0.25, 0.5, 0.75, 1.0])
dataSeries wraps both a sequence of values and a sequence of indices, which we can access with the values and index attributes.
The values are simply a familiar NumPy array:
data.valuesindex is an array-like object of type pd.Index, which we'll discuss in more detail momentarily.
data.index
data[1]
data[1:3]Series is much more general and flexible than the one-dimensional NumPy array that it emulates.Series as generalized NumPy arraySeries object is basically interchangeable with a one-dimensional NumPy array.
The essential difference is the presence of the index: while the Numpy Array has an implicitly defined integer index used to access the values, the Pandas Series has an explicitly defined index associated with the values.Series object additional capabilities. For example, the index need not be an integer, but can consist of values of any desired type.
For example, if we wish, we can use strings as an index:
data = pd.Series([0.25, 0.5, 0.75, 1.0],
index=['a', 'b', 'c', 'd'])
data
data['b']
data = pd.Series([0.25, 0.5, 0.75, 1.0],
index=[2, 5, 3, 7])
data
data[5]Series a bit like a specialization of a Python dictionary.
A dictionary is a structure that maps arbitrary keys to a set of arbitrary values, and a Series is a structure which maps typed keys to a set of typed values.
This typing is important: just as the type-specific compiled code behind a NumPy array makes it more efficient than a Python list for certain operations, the type information of a Pandas Series makes it much more efficient than Python dictionaries for certain operations.Series-as-dictionary analogy can be made even more clear by constructing a Series object directly from a Python dictionary:
population_dict = {'California': 38332521,
'Texas': 26448193,
'New York': 19651127,
'Florida': 19552860,
'Illinois': 12882135}
population = pd.Series(population_dict)
populationSeries will be created where the index is drawn from the sorted keys.
From here, typical dictionary-style item access can be performed:
population['California']Series also supports array-style operations such as slicing:
population['California':'Illinois']Series from scratch; all of them are some version of the following:
>>> pd.Series(data, index=index)index is an optional argument, and data can be one of many entities.data can be a list or NumPy array, in which case index defaults to an integer sequence:
pd.Series([2, 4, 6])data can be a scalar, which is repeated to fill the specified index:
pd.Series(5, index=[100, 200, 300])data can be a dictionary, in which index defaults to the sorted dictionary keys:
pd.Series({2:'a', 1:'b', 3:'c'})
pd.Series({2:'a', 1:'b', 3:'c'}, index=[3, 2])Series is populated only with the explicitly identified keys.DataFrame.
Like the Series object discussed in the previous section, the DataFrame can be thought of either as a generalization of a NumPy array, or as a specialization of a Python dictionary.
We'll now take a look at each of these perspectives.Series is an analog of a one-dimensional array with flexible indices, a DataFrame is an analog of a two-dimensional array with both flexible row indices and flexible column names.
Just as you might think of a two-dimensional array as an ordered sequence of aligned one-dimensional columns, you can think of a DataFrame as a sequence of aligned Series objects.
Here, by "aligned" we mean that they share the same index.Series listing the area of each of the five states discussed in the previous section:
area_dict = {'California': 423967, 'Texas': 695662, 'New York': 141297,
'Florida': 170312, 'Illinois': 149995}
area = pd.Series(area_dict)
areapopulation Series from before, we can use a dictionary to construct a single two-dimensional object containing this information:
states = pd.DataFrame({'population': population,
'area': area})
statesSeries object, the DataFrame has an index attribute that gives access to the index labels:
states.indexDataFrame has a columns attribute, which is an Index object holding the column labels:
states.columnsDataFrame can be thought of as a generalization of a two-dimensional NumPy array, where both the rows and columns have a generalized index for accessing the data.DataFrame as a specialization of a dictionary.
Where a dictionary maps a key to a value, a DataFrame maps a column name to a Series of column data.
For example, asking for the 'area' attribute returns the Series object containing the areas we saw earlier:
states['area']data[0] will return the first row. For a DataFrame, data['col0'] will return the first column.
Because of this, it is probably better to think about DataFrames as generalized dictionaries rather than generalized arrays, though both ways of looking at the situation can be useful.
We'll explore more flexible means of indexing DataFrames in Data Indexing and Selection.DataFrame can be constructed in a variety of ways.
Here we'll give several examples.DataFrame is a collection of Series objects, and a single-column DataFrame can be constructed from a single Series:
pd.DataFrame(population, columns=['population'])DataFrame.
We'll use a simple list comprehension to create some data:
data = [{'a': i, 'b': 2 * i}
for i in range(3)]
pd.DataFrame(data)NaN (i.e., "not a number") values:
pd.DataFrame([{'a': 1, 'b': 2}, {'b': 3, 'c': 4}])DataFrame can be constructed from a dictionary of Series objects as well:
pd.DataFrame({'population': population,
'area': area})DataFrame with any specified column and index names.
If omitted, an integer index will be used for each:
pd.DataFrame(np.random.rand(3, 2),
columns=['foo', 'bar'],
index=['a', 'b', 'c'])DataFrame operates much like a structured array, and can be created directly from one:
A = np.zeros(3, dtype=[('A', 'i8'), ('B', 'f8')])
A
pd.DataFrame(A)Series and DataFrame objects contain an explicit index that lets you reference and modify data.
This Index object is an interesting structure in itself, and it can be thought of either as an immutable array or as an ordered set (technically a multi-set, as Index objects may contain repeated values).
Those views have some interesting consequences in the operations available on Index objects.
As a simple example, let's construct an Index from a list of integers:
ind = pd.Index([2, 3, 5, 7, 11])
indIndex in many ways operates like an array.
For example, we can use standard Python indexing notation to retrieve values or slices:
ind[1]
ind[::2]Index objects also have many of the attributes familiar from NumPy arrays:
print(ind.size, ind.shape, ind.ndim, ind.dtype)Index objects and NumPy arrays is that indices are immutable–that is, they cannot be modified via the normal means:
ind[1] = 0TypeError Traceback (most recent call last)
<ipython-input-34-40e631c82e8a> in <module>()
----> 1 ind[1] = 0
/Users/jakevdp/anaconda/lib/python3.5/site-packages/pandas/indexes/base.py in __setitem__(self, key, value)
1243
1244 def __setitem__(self, key, value):
-> 1245 raise TypeError("Index does not support mutable operations")
1246
1247 def __getitem__(self, key):
TypeError: Index does not support mutable operations
DataFrames and arrays, without the potential for side effects from inadvertent index modification.Index object follows many of the conventions used by Python's built-in set data structure, so that unions, intersections, differences, and other combinations can be computed in a familiar way:
indA = pd.Index([1, 3, 5, 7, 9])
indB = pd.Index([2, 3, 5, 7, 11])
indA & indB # intersection
indA | indB # union
indA ^ indB # symmetric difference