- Intro to data structures
- Series
- DataFrame
- From dict of Series or dicts
- From dict of ndarrays / lists
- From structured or record array
- From a list of dicts
- From a dict of tuples
- From a Series
- Alternate constructors
- Column selection, addition, deletion
- Assigning new columns in method chains
- Indexing / selection
- Data alignment and arithmetic
- Transposing
- DataFrame interoperability with NumPy functions
- Console display
- DataFrame column attribute access and IPython completion
Intro to data structures
We’ll start with a quick, non-comprehensive overview of the fundamental datastructures in pandas to get you started. The fundamental behavior about datatypes, indexing, and axis labeling / alignment apply across all of theobjects. To get started, import NumPy and load pandas into your namespace:
- In [1]: import numpy as np
- In [2]: import pandas as pd
Here is a basic tenet to keep in mind: data alignment is intrinsic. The linkbetween labels and data will not be broken unless done so explicitly by you.
We’ll give a brief intro to the data structures, then consider all of the broadcategories of functionality and methods in separate sections.
Series
Series
is a one-dimensional labeled array capable of holding any datatype (integers, strings, floating point numbers, Python objects, etc.). The axislabels are collectively referred to as the index. The basic method to create a Series is to call:
- >>> s = pd.Series(data, index=index)
Here, data
can be many different things:
- a Python dict
- an ndarray
- a scalar value (like 5)
The passed index is a list of axis labels. Thus, this separates into a fewcases depending on what data is:
From ndarray
If data
is an ndarray, index must be the same length as data. If noindex is passed, one will be created having values [0, …, len(data) - 1]
.
- In [3]: s = pd.Series(np.random.randn(5), index=['a', 'b', 'c', 'd', 'e'])
- In [4]: s
- Out[4]:
- a 0.469112
- b -0.282863
- c -1.509059
- d -1.135632
- e 1.212112
- dtype: float64
- In [5]: s.index
- Out[5]: Index(['a', 'b', 'c', 'd', 'e'], dtype='object')
- In [6]: pd.Series(np.random.randn(5))
- Out[6]:
- 0 -0.173215
- 1 0.119209
- 2 -1.044236
- 3 -0.861849
- 4 -2.104569
- dtype: float64
Note
pandas supports non-unique index values. If an operationthat does not support duplicate index values is attempted, an exceptionwill be raised at that time. The reason for being lazy is nearly all performance-based(there are many instances in computations, like parts of GroupBy, where the indexis not used).
From dict
Series can be instantiated from dicts:
- In [7]: d = {'b': 1, 'a': 0, 'c': 2}
- In [8]: pd.Series(d)
- Out[8]:
- b 1
- a 0
- c 2
- dtype: int64
Note
When the data is a dict, and an index is not passed, the Series
indexwill be ordered by the dict’s insertion order, if you’re using Pythonversion >= 3.6 and Pandas version >= 0.23.
If you’re using Python < 3.6 or Pandas < 0.23, and an index is not passed,the Series
index will be the lexically ordered list of dict keys.
In the example above, if you were on a Python version lower than 3.6 or aPandas version lower than 0.23, the Series
would be ordered by the lexicalorder of the dict keys (i.e. ['a', 'b', 'c']
rather than ['b', 'a', 'c']
).
If an index is passed, the values in data corresponding to the labels in theindex will be pulled out.
- In [9]: d = {'a': 0., 'b': 1., 'c': 2.}
- In [10]: pd.Series(d)
- Out[10]:
- a 0.0
- b 1.0
- c 2.0
- dtype: float64
- In [11]: pd.Series(d, index=['b', 'c', 'd', 'a'])
- Out[11]:
- b 1.0
- c 2.0
- d NaN
- a 0.0
- dtype: float64
Note
NaN (not a number) is the standard missing data marker used in pandas.
From scalar value
If data
is a scalar value, an index must beprovided. The value will be repeated to match the length of index.
- In [12]: pd.Series(5., index=['a', 'b', 'c', 'd', 'e'])
- Out[12]:
- a 5.0
- b 5.0
- c 5.0
- d 5.0
- e 5.0
- dtype: float64
Series is ndarray-like
Series
acts very similarly to a ndarray
, and is a valid argument to most NumPy functions.However, operations such as slicing will also slice the index.
- In [13]: s[0]
- Out[13]: 0.4691122999071863
- In [14]: s[:3]
- Out[14]:
- a 0.469112
- b -0.282863
- c -1.509059
- dtype: float64
- In [15]: s[s > s.median()]
- Out[15]:
- a 0.469112
- e 1.212112
- dtype: float64
- In [16]: s[[4, 3, 1]]
- Out[16]:
- e 1.212112
- d -1.135632
- b -0.282863
- dtype: float64
- In [17]: np.exp(s)
- Out[17]:
- a 1.598575
- b 0.753623
- c 0.221118
- d 0.321219
- e 3.360575
- dtype: float64
Note
We will address array-based indexing like s[[4, 3, 1]]
in section.
Like a NumPy array, a pandas Series has a dtype
.
- In [18]: s.dtype
- Out[18]: dtype('float64')
This is often a NumPy dtype. However, pandas and 3rd-party librariesextend NumPy’s type system in a few places, in which case the dtype wouldbe a ExtensionDtype
. Some examples withinpandas are Categorical data and Nullable integer data type. See dtypesfor more.
If you need the actual array backing a Series
, use Series.array
.
- In [19]: s.array
- Out[19]:
- <PandasArray>
- [ 0.4691122999071863, -0.2828633443286633, -1.5090585031735124,
- -1.1356323710171934, 1.2121120250208506]
- Length: 5, dtype: float64
Accessing the array can be useful when you need to do some operation without theindex (to disable automatic alignment, for example).
Series.array
will always be an ExtensionArray
.Briefly, an ExtensionArray is a thin wrapper around one or more concrete arrays like anumpy.ndarray
. Pandas knows how to take an ExtensionArray
andstore it in a Series
or a column of a DataFrame
.See dtypes for more.
While Series is ndarray-like, if you need an actual ndarray, then useSeries.to_numpy()
.
- In [20]: s.to_numpy()
- Out[20]: array([ 0.4691, -0.2829, -1.5091, -1.1356, 1.2121])
Even if the Series is backed by a ExtensionArray
,Series.to_numpy()
will return a NumPy ndarray.
Series is dict-like
A Series is like a fixed-size dict in that you can get and set values by indexlabel:
- In [21]: s['a']
- Out[21]: 0.4691122999071863
- In [22]: s['e'] = 12.
- In [23]: s
- Out[23]:
- a 0.469112
- b -0.282863
- c -1.509059
- d -1.135632
- e 12.000000
- dtype: float64
- In [24]: 'e' in s
- Out[24]: True
- In [25]: 'f' in s
- Out[25]: False
If a label is not contained, an exception is raised:
- >>> s['f']
- KeyError: 'f'
Using the get
method, a missing label will return None or specified default:
- In [26]: s.get('f')
- In [27]: s.get('f', np.nan)
- Out[27]: nan
See also the section on attribute access.
Vectorized operations and label alignment with Series
When working with raw NumPy arrays, looping through value-by-value is usuallynot necessary. The same is true when working with Series in pandas.Series can also be passed into most NumPy methods expecting an ndarray.
- In [28]: s + s
- Out[28]:
- a 0.938225
- b -0.565727
- c -3.018117
- d -2.271265
- e 24.000000
- dtype: float64
- In [29]: s * 2
- Out[29]:
- a 0.938225
- b -0.565727
- c -3.018117
- d -2.271265
- e 24.000000
- dtype: float64
- In [30]: np.exp(s)
- Out[30]:
- a 1.598575
- b 0.753623
- c 0.221118
- d 0.321219
- e 162754.791419
- dtype: float64
A key difference between Series and ndarray is that operations between Seriesautomatically align the data based on label. Thus, you can write computationswithout giving consideration to whether the Series involved have the samelabels.
- In [31]: s[1:] + s[:-1]
- Out[31]:
- a NaN
- b -0.565727
- c -3.018117
- d -2.271265
- e NaN
- dtype: float64
The result of an operation between unaligned Series will have the union ofthe indexes involved. If a label is not found in one Series or the other, theresult will be marked as missing NaN
. Being able to write code without doingany explicit data alignment grants immense freedom and flexibility ininteractive data analysis and research. The integrated data alignment featuresof the pandas data structures set pandas apart from the majority of relatedtools for working with labeled data.
Note
In general, we chose to make the default result of operations betweendifferently indexed objects yield the union of the indexes in order toavoid loss of information. Having an index label, though the data ismissing, is typically important information as part of a computation. Youof course have the option of dropping labels with missing data via thedropna function.
Name attribute
Series can also have a name
attribute:
- In [32]: s = pd.Series(np.random.randn(5), name='something')
- In [33]: s
- Out[33]:
- 0 -0.494929
- 1 1.071804
- 2 0.721555
- 3 -0.706771
- 4 -1.039575
- Name: something, dtype: float64
- In [34]: s.name
- Out[34]: 'something'
The Series name
will be assigned automatically in many cases, in particularwhen taking 1D slices of DataFrame as you will see below.
New in version 0.18.0.
You can rename a Series with the pandas.Series.rename()
method.
- In [35]: s2 = s.rename("different")
- In [36]: s2.name
- Out[36]: 'different'
Note that s
and s2
refer to different objects.
DataFrame
DataFrame is a 2-dimensional labeled data structure with columns ofpotentially different types. You can think of it like a spreadsheet or SQLtable, or a dict of Series objects. It is generally the most commonly usedpandas object. Like Series, DataFrame accepts many different kinds of input:
- Dict of 1D ndarrays, lists, dicts, or Series
- 2-D numpy.ndarray
- Structured or record ndarray
- A
Series
- Another
DataFrame
Along with the data, you can optionally pass index (row labels) andcolumns (column labels) arguments. If you pass an index and / or columns,you are guaranteeing the index and / or columns of the resultingDataFrame. Thus, a dict of Series plus a specific index will discard all datanot matching up to the passed index.
If axis labels are not passed, they will be constructed from the input databased on common sense rules.
Note
When the data is a dict, and columns
is not specified, the DataFrame
columns will be ordered by the dict’s insertion order, if you are usingPython version >= 3.6 and Pandas >= 0.23.
If you are using Python < 3.6 or Pandas < 0.23, and columns
is notspecified, the DataFrame
columns will be the lexically ordered list of dictkeys.
From dict of Series or dicts
The resulting index will be the union of the indexes of the variousSeries. If there are any nested dicts, these will first be converted toSeries. If no columns are passed, the columns will be the ordered list of dictkeys.
- In [37]: d = {'one': pd.Series([1., 2., 3.], index=['a', 'b', 'c']),
- ....: 'two': pd.Series([1., 2., 3., 4.], index=['a', 'b', 'c', 'd'])}
- ....:
- In [38]: df = pd.DataFrame(d)
- In [39]: df
- Out[39]:
- one two
- a 1.0 1.0
- b 2.0 2.0
- c 3.0 3.0
- d NaN 4.0
- In [40]: pd.DataFrame(d, index=['d', 'b', 'a'])
- Out[40]:
- one two
- d NaN 4.0
- b 2.0 2.0
- a 1.0 1.0
- In [41]: pd.DataFrame(d, index=['d', 'b', 'a'], columns=['two', 'three'])
- Out[41]:
- two three
- d 4.0 NaN
- b 2.0 NaN
- a 1.0 NaN
The row and column labels can be accessed respectively by accessing theindex and columns attributes:
Note
When a particular set of columns is passed along with a dict of data, thepassed columns override the keys in the dict.
- In [42]: df.index
- Out[42]: Index(['a', 'b', 'c', 'd'], dtype='object')
- In [43]: df.columns
- Out[43]: Index(['one', 'two'], dtype='object')
From dict of ndarrays / lists
The ndarrays must all be the same length. If an index is passed, it mustclearly also be the same length as the arrays. If no index is passed, theresult will be range(n)
, where n
is the array length.
- In [44]: d = {'one': [1., 2., 3., 4.],
- ....: 'two': [4., 3., 2., 1.]}
- ....:
- In [45]: pd.DataFrame(d)
- Out[45]:
- one two
- 0 1.0 4.0
- 1 2.0 3.0
- 2 3.0 2.0
- 3 4.0 1.0
- In [46]: pd.DataFrame(d, index=['a', 'b', 'c', 'd'])
- Out[46]:
- one two
- a 1.0 4.0
- b 2.0 3.0
- c 3.0 2.0
- d 4.0 1.0
From structured or record array
This case is handled identically to a dict of arrays.
- In [47]: data = np.zeros((2, ), dtype=[('A', 'i4'), ('B', 'f4'), ('C', 'a10')])
- In [48]: data[:] = [(1, 2., 'Hello'), (2, 3., "World")]
- In [49]: pd.DataFrame(data)
- Out[49]:
- A B C
- 0 1 2.0 b'Hello'
- 1 2 3.0 b'World'
- In [50]: pd.DataFrame(data, index=['first', 'second'])
- Out[50]:
- A B C
- first 1 2.0 b'Hello'
- second 2 3.0 b'World'
- In [51]: pd.DataFrame(data, columns=['C', 'A', 'B'])
- Out[51]:
- C A B
- 0 b'Hello' 1 2.0
- 1 b'World' 2 3.0
Note
DataFrame is not intended to work exactly like a 2-dimensional NumPyndarray.
From a list of dicts
- In [52]: data2 = [{'a': 1, 'b': 2}, {'a': 5, 'b': 10, 'c': 20}]
- In [53]: pd.DataFrame(data2)
- Out[53]:
- a b c
- 0 1 2 NaN
- 1 5 10 20.0
- In [54]: pd.DataFrame(data2, index=['first', 'second'])
- Out[54]:
- a b c
- first 1 2 NaN
- second 5 10 20.0
- In [55]: pd.DataFrame(data2, columns=['a', 'b'])
- Out[55]:
- a b
- 0 1 2
- 1 5 10
From a dict of tuples
You can automatically create a MultiIndexed frame by passing a tuplesdictionary.
- In [56]: pd.DataFrame({('a', 'b'): {('A', 'B'): 1, ('A', 'C'): 2},
- ....: ('a', 'a'): {('A', 'C'): 3, ('A', 'B'): 4},
- ....: ('a', 'c'): {('A', 'B'): 5, ('A', 'C'): 6},
- ....: ('b', 'a'): {('A', 'C'): 7, ('A', 'B'): 8},
- ....: ('b', 'b'): {('A', 'D'): 9, ('A', 'B'): 10}})
- ....:
- Out[56]:
- a b
- b a c a b
- A B 1.0 4.0 5.0 8.0 10.0
- C 2.0 3.0 6.0 7.0 NaN
- D NaN NaN NaN NaN 9.0
From a Series
The result will be a DataFrame with the same index as the input Series, andwith one column whose name is the original name of the Series (only if no othercolumn name provided).
Missing data
Much more will be said on this topic in the Missing datasection. To construct a DataFrame with missing data, we use np.nan
torepresent missing values. Alternatively, you may pass a numpy.MaskedArray
as the data argument to the DataFrame constructor, and its masked entries willbe considered missing.
Alternate constructors
DataFrame.from_dict
DataFrame.from_dict
takes a dict of dicts or a dict of array-like sequencesand returns a DataFrame. It operates like the DataFrame
constructor exceptfor the orient
parameter which is 'columns'
by default, but which can beset to 'index'
in order to use the dict keys as row labels.
- In [57]: pd.DataFrame.from_dict(dict([('A', [1, 2, 3]), ('B', [4, 5, 6])]))
- Out[57]:
- A B
- 0 1 4
- 1 2 5
- 2 3 6
If you pass orient='index'
, the keys will be the row labels. In thiscase, you can also pass the desired column names:
- In [58]: pd.DataFrame.from_dict(dict([('A', [1, 2, 3]), ('B', [4, 5, 6])]),
- ....: orient='index', columns=['one', 'two', 'three'])
- ....:
- Out[58]:
- one two three
- A 1 2 3
- B 4 5 6
DataFrame.from_records
DataFrame.from_records
takes a list of tuples or an ndarray with structureddtype. It works analogously to the normal DataFrame
constructor, except thatthe resulting DataFrame index may be a specific field of the structureddtype. For example:
- In [59]: data
- Out[59]:
- array([(1, 2., b'Hello'), (2, 3., b'World')],
- dtype=[('A', '<i4'), ('B', '<f4'), ('C', 'S10')])
- In [60]: pd.DataFrame.from_records(data, index='C')
- Out[60]:
- A B
- C
- b'Hello' 1 2.0
- b'World' 2 3.0
Column selection, addition, deletion
You can treat a DataFrame semantically like a dict of like-indexed Seriesobjects. Getting, setting, and deleting columns works with the same syntax asthe analogous dict operations:
- In [61]: df['one']
- Out[61]:
- a 1.0
- b 2.0
- c 3.0
- d NaN
- Name: one, dtype: float64
- In [62]: df['three'] = df['one'] * df['two']
- In [63]: df['flag'] = df['one'] > 2
- In [64]: df
- Out[64]:
- one two three flag
- a 1.0 1.0 1.0 False
- b 2.0 2.0 4.0 False
- c 3.0 3.0 9.0 True
- d NaN 4.0 NaN False
Columns can be deleted or popped like with a dict:
- In [65]: del df['two']
- In [66]: three = df.pop('three')
- In [67]: df
- Out[67]:
- one flag
- a 1.0 False
- b 2.0 False
- c 3.0 True
- d NaN False
When inserting a scalar value, it will naturally be propagated to fill thecolumn:
- In [68]: df['foo'] = 'bar'
- In [69]: df
- Out[69]:
- one flag foo
- a 1.0 False bar
- b 2.0 False bar
- c 3.0 True bar
- d NaN False bar
When inserting a Series that does not have the same index as the DataFrame, itwill be conformed to the DataFrame’s index:
- In [70]: df['one_trunc'] = df['one'][:2]
- In [71]: df
- Out[71]:
- one flag foo one_trunc
- a 1.0 False bar 1.0
- b 2.0 False bar 2.0
- c 3.0 True bar NaN
- d NaN False bar NaN
You can insert raw ndarrays but their length must match the length of theDataFrame’s index.
By default, columns get inserted at the end. The insert
function isavailable to insert at a particular location in the columns:
- In [72]: df.insert(1, 'bar', df['one'])
- In [73]: df
- Out[73]:
- one bar flag foo one_trunc
- a 1.0 1.0 False bar 1.0
- b 2.0 2.0 False bar 2.0
- c 3.0 3.0 True bar NaN
- d NaN NaN False bar NaN
Assigning new columns in method chains
Inspired by dplyr’smutate
verb, DataFrame has an assign()
method that allows you to easily create new columns that are potentiallyderived from existing columns.
- In [74]: iris = pd.read_csv('data/iris.data')
- In [75]: iris.head()
- Out[75]:
- SepalLength SepalWidth PetalLength PetalWidth Name
- 0 5.1 3.5 1.4 0.2 Iris-setosa
- 1 4.9 3.0 1.4 0.2 Iris-setosa
- 2 4.7 3.2 1.3 0.2 Iris-setosa
- 3 4.6 3.1 1.5 0.2 Iris-setosa
- 4 5.0 3.6 1.4 0.2 Iris-setosa
- In [76]: (iris.assign(sepal_ratio=iris['SepalWidth'] / iris['SepalLength'])
- ....: .head())
- ....:
- Out[76]:
- SepalLength SepalWidth PetalLength PetalWidth Name sepal_ratio
- 0 5.1 3.5 1.4 0.2 Iris-setosa 0.686275
- 1 4.9 3.0 1.4 0.2 Iris-setosa 0.612245
- 2 4.7 3.2 1.3 0.2 Iris-setosa 0.680851
- 3 4.6 3.1 1.5 0.2 Iris-setosa 0.673913
- 4 5.0 3.6 1.4 0.2 Iris-setosa 0.720000
In the example above, we inserted a precomputed value. We can also pass ina function of one argument to be evaluated on the DataFrame being assigned to.
- In [77]: iris.assign(sepal_ratio=lambda x: (x['SepalWidth'] / x['SepalLength'])).head()
- Out[77]:
- SepalLength SepalWidth PetalLength PetalWidth Name sepal_ratio
- 0 5.1 3.5 1.4 0.2 Iris-setosa 0.686275
- 1 4.9 3.0 1.4 0.2 Iris-setosa 0.612245
- 2 4.7 3.2 1.3 0.2 Iris-setosa 0.680851
- 3 4.6 3.1 1.5 0.2 Iris-setosa 0.673913
- 4 5.0 3.6 1.4 0.2 Iris-setosa 0.720000
assign
always returns a copy of the data, leaving the originalDataFrame untouched.
Passing a callable, as opposed to an actual value to be inserted, isuseful when you don’t have a reference to the DataFrame at hand. This iscommon when using assign
in a chain of operations. For example,we can limit the DataFrame to just those observations with a Sepal Lengthgreater than 5, calculate the ratio, and plot:
- In [78]: (iris.query('SepalLength > 5')
- ....: .assign(SepalRatio=lambda x: x.SepalWidth / x.SepalLength,
- ....: PetalRatio=lambda x: x.PetalWidth / x.PetalLength)
- ....: .plot(kind='scatter', x='SepalRatio', y='PetalRatio'))
- ....:
- Out[78]: <matplotlib.axes._subplots.AxesSubplot at 0x7f453ca05f90>
Since a function is passed in, the function is computed on the DataFramebeing assigned to. Importantly, this is the DataFrame that’s been filteredto those rows with sepal length greater than 5. The filtering happens first,and then the ratio calculations. This is an example where we didn’thave a reference to the filtered DataFrame available.
The function signature for assign
is simply **kwargs
. The keysare the column names for the new fields, and the values are either a valueto be inserted (for example, a Series
or NumPy array), or a functionof one argument to be called on the DataFrame
. A copy of the originalDataFrame is returned, with the new values inserted.
Changed in version 0.23.0.
Starting with Python 3.6 the order of kwargs
is preserved. This allowsfor dependent assignment, where an expression later in kwargs
can referto a column created earlier in the same assign()
.
- In [79]: dfa = pd.DataFrame({"A": [1, 2, 3],
- ....: "B": [4, 5, 6]})
- ....:
- In [80]: dfa.assign(C=lambda x: x['A'] + x['B'],
- ....: D=lambda x: x['A'] + x['C'])
- ....:
- Out[80]:
- A B C D
- 0 1 4 5 6
- 1 2 5 7 9
- 2 3 6 9 12
In the second expression, x['C']
will refer to the newly created column,that’s equal to dfa['A'] + dfa['B']
.
To write code compatible with all versions of Python, split the assignment in two.
- In [81]: dependent = pd.DataFrame({"A": [1, 1, 1]})
- In [82]: (dependent.assign(A=lambda x: x['A'] + 1)
- ....: .assign(B=lambda x: x['A'] + 2))
- ....:
- Out[82]:
- A B
- 0 2 4
- 1 2 4
- 2 2 4
Warning
Dependent assignment may subtly change the behavior of your code betweenPython 3.6 and older versions of Python.
If you wish to write code that supports versions of python before and after 3.6,you’ll need to take care when passing assign
expressions that
- Update an existing column
- Refer to the newly updated column in the same
assign
For example, we’ll update column “A” and then refer to it when creating “B”.
- >>> dependent = pd.DataFrame({"A": [1, 1, 1]})
- >>> dependent.assign(A=lambda x: x["A"] + 1, B=lambda x: x["A"] + 2)
For Python 3.5 and earlier the expression creating B
refers to the“old” value of A
, [1, 1, 1]
. The output is then
- A B
- 0 2 3
- 1 2 3
- 2 2 3
For Python 3.6 and later, the expression creating A
refers to the“new” value of A
, [2, 2, 2]
, which results in
- A B
- 0 2 4
- 1 2 4
- 2 2 4
Indexing / selection
The basics of indexing are as follows:
Operation | Syntax | Result |
---|---|---|
Select column | df[col] | Series |
Select row by label | df.loc[label] | Series |
Select row by integer location | df.iloc[loc] | Series |
Slice rows | df[5:10] | DataFrame |
Select rows by boolean vector | df[bool_vec] | DataFrame |
Row selection, for example, returns a Series whose index is the columns of theDataFrame:
- In [83]: df.loc['b']
- Out[83]:
- one 2
- bar 2
- flag False
- foo bar
- one_trunc 2
- Name: b, dtype: object
- In [84]: df.iloc[2]
- Out[84]:
- one 3
- bar 3
- flag True
- foo bar
- one_trunc NaN
- Name: c, dtype: object
For a more exhaustive treatment of sophisticated label-based indexing andslicing, see the section on indexing. We will address thefundamentals of reindexing / conforming to new sets of labels in thesection on reindexing.
Data alignment and arithmetic
Data alignment between DataFrame objects automatically align on both thecolumns and the index (row labels). Again, the resulting object will have theunion of the column and row labels.
- In [85]: df = pd.DataFrame(np.random.randn(10, 4), columns=['A', 'B', 'C', 'D'])
- In [86]: df2 = pd.DataFrame(np.random.randn(7, 3), columns=['A', 'B', 'C'])
- In [87]: df + df2
- Out[87]:
- A B C D
- 0 0.045691 -0.014138 1.380871 NaN
- 1 -0.955398 -1.501007 0.037181 NaN
- 2 -0.662690 1.534833 -0.859691 NaN
- 3 -2.452949 1.237274 -0.133712 NaN
- 4 1.414490 1.951676 -2.320422 NaN
- 5 -0.494922 -1.649727 -1.084601 NaN
- 6 -1.047551 -0.748572 -0.805479 NaN
- 7 NaN NaN NaN NaN
- 8 NaN NaN NaN NaN
- 9 NaN NaN NaN NaN
When doing an operation between DataFrame and Series, the default behavior isto align the Series index on the DataFrame columns, thus broadcastingrow-wise. For example:
- In [88]: df - df.iloc[0]
- Out[88]:
- A B C D
- 0 0.000000 0.000000 0.000000 0.000000
- 1 -1.359261 -0.248717 -0.453372 -1.754659
- 2 0.253128 0.829678 0.010026 -1.991234
- 3 -1.311128 0.054325 -1.724913 -1.620544
- 4 0.573025 1.500742 -0.676070 1.367331
- 5 -1.741248 0.781993 -1.241620 -2.053136
- 6 -1.240774 -0.869551 -0.153282 0.000430
- 7 -0.743894 0.411013 -0.929563 -0.282386
- 8 -1.194921 1.320690 0.238224 -1.482644
- 9 2.293786 1.856228 0.773289 -1.446531
In the special case of working with time series data, if the DataFrame indexcontains dates, the broadcasting will be column-wise:
- In [89]: index = pd.date_range('1/1/2000', periods=8)
- In [90]: df = pd.DataFrame(np.random.randn(8, 3), index=index, columns=list('ABC'))
- In [91]: df
- Out[91]:
- A B C
- 2000-01-01 -1.226825 0.769804 -1.281247
- 2000-01-02 -0.727707 -0.121306 -0.097883
- 2000-01-03 0.695775 0.341734 0.959726
- 2000-01-04 -1.110336 -0.619976 0.149748
- 2000-01-05 -0.732339 0.687738 0.176444
- 2000-01-06 0.403310 -0.154951 0.301624
- 2000-01-07 -2.179861 -1.369849 -0.954208
- 2000-01-08 1.462696 -1.743161 -0.826591
- In [92]: type(df['A'])
- Out[92]: pandas.core.series.Series
- In [93]: df - df['A']
- Out[93]:
- 2000-01-01 00:00:00 2000-01-02 00:00:00 2000-01-03 00:00:00 2000-01-04 00:00:00 2000-01-05 00:00:00 2000-01-06 00:00:00 2000-01-07 00:00:00 2000-01-08 00:00:00 A B C
- 2000-01-01 NaN NaN NaN NaN NaN NaN NaN NaN NaN NaN NaN
- 2000-01-02 NaN NaN NaN NaN NaN NaN NaN NaN NaN NaN NaN
- 2000-01-03 NaN NaN NaN NaN NaN NaN NaN NaN NaN NaN NaN
- 2000-01-04 NaN NaN NaN NaN NaN NaN NaN NaN NaN NaN NaN
- 2000-01-05 NaN NaN NaN NaN NaN NaN NaN NaN NaN NaN NaN
- 2000-01-06 NaN NaN NaN NaN NaN NaN NaN NaN NaN NaN NaN
- 2000-01-07 NaN NaN NaN NaN NaN NaN NaN NaN NaN NaN NaN
- 2000-01-08 NaN NaN NaN NaN NaN NaN NaN NaN NaN NaN NaN
Warning
- df - df['A']
is now deprecated and will be removed in a future release. The preferred wayto replicate this behavior is
- df.sub(df['A'], axis=0)
For explicit control over the matching and broadcasting behavior, see thesection on flexible binary operations.
Operations with scalars are just as you would expect:
- In [94]: df * 5 + 2
- Out[94]:
- A B C
- 2000-01-01 -4.134126 5.849018 -4.406237
- 2000-01-02 -1.638535 1.393469 1.510587
- 2000-01-03 5.478873 3.708672 6.798628
- 2000-01-04 -3.551681 -1.099880 2.748742
- 2000-01-05 -1.661697 5.438692 2.882222
- 2000-01-06 4.016548 1.225246 3.508122
- 2000-01-07 -8.899303 -4.849247 -2.771039
- 2000-01-08 9.313480 -6.715805 -2.132955
- In [95]: 1 / df
- Out[95]:
- A B C
- 2000-01-01 -0.815112 1.299033 -0.780489
- 2000-01-02 -1.374179 -8.243600 -10.216313
- 2000-01-03 1.437247 2.926250 1.041965
- 2000-01-04 -0.900628 -1.612966 6.677871
- 2000-01-05 -1.365487 1.454041 5.667510
- 2000-01-06 2.479485 -6.453662 3.315381
- 2000-01-07 -0.458745 -0.730007 -1.047990
- 2000-01-08 0.683669 -0.573671 -1.209788
- In [96]: df ** 4
- Out[96]:
- A B C
- 2000-01-01 2.265327 0.351172 2.694833
- 2000-01-02 0.280431 0.000217 0.000092
- 2000-01-03 0.234355 0.013638 0.848376
- 2000-01-04 1.519910 0.147740 0.000503
- 2000-01-05 0.287640 0.223714 0.000969
- 2000-01-06 0.026458 0.000576 0.008277
- 2000-01-07 22.579530 3.521204 0.829033
- 2000-01-08 4.577374 9.233151 0.466834
Boolean operators work as well:
- In [97]: df1 = pd.DataFrame({'a': [1, 0, 1], 'b': [0, 1, 1]}, dtype=bool)
- In [98]: df2 = pd.DataFrame({'a': [0, 1, 1], 'b': [1, 1, 0]}, dtype=bool)
- In [99]: df1 & df2
- Out[99]:
- a b
- 0 False False
- 1 False True
- 2 True False
- In [100]: df1 | df2
- Out[100]:
- a b
- 0 True True
- 1 True True
- 2 True True
- In [101]: df1 ^ df2
- Out[101]:
- a b
- 0 True True
- 1 True False
- 2 False True
- In [102]: -df1
- Out[102]:
- a b
- 0 False True
- 1 True False
- 2 False False
Transposing
To transpose, access the T
attribute (also the transpose
function),similar to an ndarray:
- # only show the first 5 rows
- In [103]: df[:5].T
- Out[103]:
- 2000-01-01 2000-01-02 2000-01-03 2000-01-04 2000-01-05
- A -1.226825 -0.727707 0.695775 -1.110336 -0.732339
- B 0.769804 -0.121306 0.341734 -0.619976 0.687738
- C -1.281247 -0.097883 0.959726 0.149748 0.176444
DataFrame interoperability with NumPy functions
Elementwise NumPy ufuncs (log, exp, sqrt, …) and various other NumPy functionscan be used with no issues on Series and DataFrame, assuming the data withinare numeric:
- In [104]: np.exp(df)
- Out[104]:
- A B C
- 2000-01-01 0.293222 2.159342 0.277691
- 2000-01-02 0.483015 0.885763 0.906755
- 2000-01-03 2.005262 1.407386 2.610980
- 2000-01-04 0.329448 0.537957 1.161542
- 2000-01-05 0.480783 1.989212 1.192968
- 2000-01-06 1.496770 0.856457 1.352053
- 2000-01-07 0.113057 0.254145 0.385117
- 2000-01-08 4.317584 0.174966 0.437538
- In [105]: np.asarray(df)
- Out[105]:
- array([[-1.2268, 0.7698, -1.2812],
- [-0.7277, -0.1213, -0.0979],
- [ 0.6958, 0.3417, 0.9597],
- [-1.1103, -0.62 , 0.1497],
- [-0.7323, 0.6877, 0.1764],
- [ 0.4033, -0.155 , 0.3016],
- [-2.1799, -1.3698, -0.9542],
- [ 1.4627, -1.7432, -0.8266]])
DataFrame is not intended to be a drop-in replacement for ndarray as itsindexing semantics and data model are quite different in places from an n-dimensionalarray.
Series
implements array_ufunc
, which allows it to work with NumPy’suniversal functions.
The ufunc is applied to the underlying array in a Series.
- In [106]: ser = pd.Series([1, 2, 3, 4])
- In [107]: np.exp(ser)
- Out[107]:
- 0 2.718282
- 1 7.389056
- 2 20.085537
- 3 54.598150
- dtype: float64
Changed in version 0.25.0: When multiple Series
are passed to a ufunc, they are aligned beforeperforming the operation.
Like other parts of the library, pandas will automatically align labeled inputsas part of a ufunc with multiple inputs. For example, using numpy.remainder()
on two Series
with differently ordered labels will align before the operation.
- In [108]: ser1 = pd.Series([1, 2, 3], index=['a', 'b', 'c'])
- In [109]: ser2 = pd.Series([1, 3, 5], index=['b', 'a', 'c'])
- In [110]: ser1
- Out[110]:
- a 1
- b 2
- c 3
- dtype: int64
- In [111]: ser2
- Out[111]:
- b 1
- a 3
- c 5
- dtype: int64
- In [112]: np.remainder(ser1, ser2)
- Out[112]:
- a 1
- b 0
- c 3
- dtype: int64
As usual, the union of the two indices is taken, and non-overlapping values are filledwith missing values.
- In [113]: ser3 = pd.Series([2, 4, 6], index=['b', 'c', 'd'])
- In [114]: ser3
- Out[114]:
- b 2
- c 4
- d 6
- dtype: int64
- In [115]: np.remainder(ser1, ser3)
- Out[115]:
- a NaN
- b 0.0
- c 3.0
- d NaN
- dtype: float64
When a binary ufunc is applied to a Series
and Index
, the Seriesimplementation takes precedence and a Series is returned.
- In [116]: ser = pd.Series([1, 2, 3])
- In [117]: idx = pd.Index([4, 5, 6])
- In [118]: np.maximum(ser, idx)
- Out[118]:
- 0 4
- 1 5
- 2 6
- dtype: int64
NumPy ufuncs are safe to apply to Series
backed by non-ndarray arrays,for example SparseArray
(see Sparse calculation). If possible,the ufunc is applied without converting the underlying data to an ndarray.
Console display
Very large DataFrames will be truncated to display them in the console.You can also get a summary using info()
.(Here I am reading a CSV version of the baseball dataset from the plyrR package):
- In [119]: baseball = pd.read_csv('data/baseball.csv')
- In [120]: print(baseball)
- id player year stint team lg g ab r h X2b X3b hr rbi sb cs bb so ibb hbp sh sf gidp
- 0 88641 womacto01 2006 2 CHN NL 19 50 6 14 1 0 1 2.0 1.0 1.0 4 4.0 0.0 0.0 3.0 0.0 0.0
- 1 88643 schilcu01 2006 1 BOS AL 31 2 0 1 0 0 0 0.0 0.0 0.0 0 1.0 0.0 0.0 0.0 0.0 0.0
- .. ... ... ... ... ... .. .. ... .. ... ... ... .. ... ... ... .. ... ... ... ... ... ...
- 98 89533 aloumo01 2007 1 NYN NL 87 328 51 112 19 1 13 49.0 3.0 0.0 27 30.0 5.0 2.0 0.0 3.0 13.0
- 99 89534 alomasa02 2007 1 NYN NL 8 22 1 3 1 0 0 0.0 0.0 0.0 0 3.0 0.0 0.0 0.0 0.0 0.0
- [100 rows x 23 columns]
- In [121]: baseball.info()
- <class 'pandas.core.frame.DataFrame'>
- RangeIndex: 100 entries, 0 to 99
- Data columns (total 23 columns):
- id 100 non-null int64
- player 100 non-null object
- year 100 non-null int64
- stint 100 non-null int64
- team 100 non-null object
- lg 100 non-null object
- g 100 non-null int64
- ab 100 non-null int64
- r 100 non-null int64
- h 100 non-null int64
- X2b 100 non-null int64
- X3b 100 non-null int64
- hr 100 non-null int64
- rbi 100 non-null float64
- sb 100 non-null float64
- cs 100 non-null float64
- bb 100 non-null int64
- so 100 non-null float64
- ibb 100 non-null float64
- hbp 100 non-null float64
- sh 100 non-null float64
- sf 100 non-null float64
- gidp 100 non-null float64
- dtypes: float64(9), int64(11), object(3)
- memory usage: 18.1+ KB
However, using to_string
will return a string representation of theDataFrame in tabular form, though it won’t always fit the console width:
- In [122]: print(baseball.iloc[-20:, :12].to_string())
- id player year stint team lg g ab r h X2b X3b
- 80 89474 finlest01 2007 1 COL NL 43 94 9 17 3 0
- 81 89480 embreal01 2007 1 OAK AL 4 0 0 0 0 0
- 82 89481 edmonji01 2007 1 SLN NL 117 365 39 92 15 2
- 83 89482 easleda01 2007 1 NYN NL 76 193 24 54 6 0
- 84 89489 delgaca01 2007 1 NYN NL 139 538 71 139 30 0
- 85 89493 cormirh01 2007 1 CIN NL 6 0 0 0 0 0
- 86 89494 coninje01 2007 2 NYN NL 21 41 2 8 2 0
- 87 89495 coninje01 2007 1 CIN NL 80 215 23 57 11 1
- 88 89497 clemero02 2007 1 NYA AL 2 2 0 1 0 0
- 89 89498 claytro01 2007 2 BOS AL 8 6 1 0 0 0
- 90 89499 claytro01 2007 1 TOR AL 69 189 23 48 14 0
- 91 89501 cirilje01 2007 2 ARI NL 28 40 6 8 4 0
- 92 89502 cirilje01 2007 1 MIN AL 50 153 18 40 9 2
- 93 89521 bondsba01 2007 1 SFN NL 126 340 75 94 14 0
- 94 89523 biggicr01 2007 1 HOU NL 141 517 68 130 31 3
- 95 89525 benitar01 2007 2 FLO NL 34 0 0 0 0 0
- 96 89526 benitar01 2007 1 SFN NL 19 0 0 0 0 0
- 97 89530 ausmubr01 2007 1 HOU NL 117 349 38 82 16 3
- 98 89533 aloumo01 2007 1 NYN NL 87 328 51 112 19 1
- 99 89534 alomasa02 2007 1 NYN NL 8 22 1 3 1 0
Wide DataFrames will be printed across multiple rows bydefault:
- In [123]: pd.DataFrame(np.random.randn(3, 12))
- Out[123]:
- 0 1 2 3 4 5 6 7 8 9 10 11
- 0 -0.345352 1.314232 0.690579 0.995761 2.396780 0.014871 3.357427 -0.317441 -1.236269 0.896171 -0.487602 -0.082240
- 1 -2.182937 0.380396 0.084844 0.432390 1.519970 -0.493662 0.600178 0.274230 0.132885 -0.023688 2.410179 1.450520
- 2 0.206053 -0.251905 -2.213588 1.063327 1.266143 0.299368 -0.863838 0.408204 -1.048089 -0.025747 -0.988387 0.094055
You can change how much to print on a single row by setting the display.width
option:
- In [124]: pd.set_option('display.width', 40) # default is 80
- In [125]: pd.DataFrame(np.random.randn(3, 12))
- Out[125]:
- 0 1 2 3 4 5 6 7 8 9 10 11
- 0 1.262731 1.289997 0.082423 -0.055758 0.536580 -0.489682 0.369374 -0.034571 -2.484478 -0.281461 0.030711 0.109121
- 1 1.126203 -0.977349 1.474071 -0.064034 -1.282782 0.781836 -1.071357 0.441153 2.353925 0.583787 0.221471 -0.744471
- 2 0.758527 1.729689 -0.964980 -0.845696 -1.340896 1.846883 -1.328865 1.682706 -1.717693 0.888782 0.228440 0.901805
You can adjust the max width of the individual columns by setting display.max_colwidth
- In [126]: datafile = {'filename': ['filename_01', 'filename_02'],
- .....: 'path': ["media/user_name/storage/folder_01/filename_01",
- .....: "media/user_name/storage/folder_02/filename_02"]}
- .....:
- In [127]: pd.set_option('display.max_colwidth', 30)
- In [128]: pd.DataFrame(datafile)
- Out[128]:
- filename path
- 0 filename_01 media/user_name/storage/fo...
- 1 filename_02 media/user_name/storage/fo...
- In [129]: pd.set_option('display.max_colwidth', 100)
- In [130]: pd.DataFrame(datafile)
- Out[130]:
- filename path
- 0 filename_01 media/user_name/storage/folder_01/filename_01
- 1 filename_02 media/user_name/storage/folder_02/filename_02
You can also disable this feature via the expand_frame_repr
option.This will print the table in one block.
DataFrame column attribute access and IPython completion
If a DataFrame column label is a valid Python variable name, the column can beaccessed like an attribute:
- In [131]: df = pd.DataFrame({'foo1': np.random.randn(5),
- .....: 'foo2': np.random.randn(5)})
- .....:
- In [132]: df
- Out[132]:
- foo1 foo2
- 0 1.171216 -0.858447
- 1 0.520260 0.306996
- 2 -1.197071 -0.028665
- 3 -1.066969 0.384316
- 4 -0.303421 1.574159
- In [133]: df.foo1
- Out[133]:
- 0 1.171216
- 1 0.520260
- 2 -1.197071
- 3 -1.066969
- 4 -0.303421
- Name: foo1, dtype: float64
The columns are also connected to the IPythoncompletion mechanism so they can be tab-completed:
- In [5]: df.fo<TAB> # noqa: E225, E999
- df.foo1 df.foo2