I have some data in text file that I am reading into Pandas. A simplified version of the txt read in is:
idx_level1|idx_level2|idx_level3|idx_level4|START_NODE|END_NODE|OtherData...
353386066294006|1142|2018-09-20T07:57:26Z|1|18260004567689|18260005575180|...
353386066294006|1142|2018-09-20T07:57:26Z|2|18260004567689|18260004240718|...
353386066294006|1142|2018-09-20T07:57:26Z|3|18260005359901|18260004567689|...
353386066294006|1142|2018-09-20T07:57:31Z|1|18260004567689|18260005575180|...
353386066294006|1142|2018-09-20T07:57:31Z|2|18260004567689|18260004240718|...
353386066294006|1142|2018-09-20T07:57:31Z|3|18260005359901|18260004567689|...
353386066294006|1142|2018-09-20T07:57:36Z|1|18260004567689|18260005575180|...
353386066294006|1142|2018-09-20T07:57:36Z|2|18260004567689|18260004240718|...
353386066294006|1142|2018-09-20T07:57:36Z|3|18260005359901|18260004567689|...
353386066736543|22|2018-04-17T07:08:23Z||||...
353386066736543|22|2018-04-17T07:08:24Z||||...
353386066736543|22|2018-04-17T07:08:25Z||||...
353386066736543|22|2018-04-17T07:08:26Z||||...
353386066736543|403|2018-07-02T16:55:07Z|1|18260004580350|18260005235340|...
...
And the code I use to read in is as follows:
mydata = pd.read_csv('/myloc/my_simple_data.txt', sep='|',
dtype={'idx_level1': 'int',
'idx_level2': 'int',
'idx_level3': 'str',
'idx_level4': 'float',
'START_NODE': 'str',
'END_NODE': 'str',
'OtherData...': 'str'},
parse_dates = ['idx_level3'],
index_col=['idx_level1','idx_level2','idx_level3','idx_level4'])
At some point I unstack this data:
temp_df = mydata.loc[(slice(None)),['START_NODE', 'END_NODE', 'OtherData...']].unstack()
My Data now looks like
START_NODE ... OtherData...
idx_level4 1.0 2.0 3.0 ... 25.0 26.0 27.0 28.0 29.0 30.0 31.0 32.0
idx_level1 idx_level2 idx_level3 ...
353386066294006 1033 2018-09-03 14:52:27 18260004553260 18260005729143 18260004553259 ... NaN NaN NaN NaN NaN NaN NaN NaN
2018-09-03 14:52:32 18260004553260 18260005729143 18260004553259 ... NaN NaN NaN NaN NaN NaN NaN NaN
2018-09-03 14:52:37 18260004553260 18260005729143 18260004553259 ... NaN NaN NaN NaN NaN NaN NaN NaN
2018-09-03 14:52:42 18260004553260 18260005729143 18260004553259 ... NaN NaN NaN NaN NaN NaN NaN NaN
2018-09-03 14:52:47 18260004553260 18260005729143 18260004553259 ... NaN NaN NaN NaN NaN NaN NaN NaN
2018-09-03 14:52:52 18260004553260 18260005729143 18260004553259 ... NaN NaN NaN NaN NaN NaN NaN NaN
2018-09-03 14:52:57 18260004553260 18260005729143 18260004553259 ... NaN NaN NaN NaN NaN NaN NaN NaN
...
Is there now a way that I can select specific columns to apply some action on - say I wanted to shift(1) on the 'START_NODE' column where it has idx_level4 = 1.0?
You can select by tuple:
s = df[('START_NODE', 4.0)].shift(1)
EDIT:
For multiple Multiindex columns use boolean indexing with loc for select columns by mask:
mux = pd.MultiIndex.from_product([['START_NODE','END_NODE'], range(1, 5)])
df = pd.DataFrame([[1] * 8], columns=mux)
print (df)
START_NODE END_NODE
1 2 3 4 1 2 3 4
0 1 1 1 1 1 1 1 1
v = [('START_NODE', 4.0), ('END_NODE', 3.0)]
df1 = df.loc[:, df.columns.isin(v)]
print (df1)
START_NODE END_NODE
4 3
0 1 1
Related
I created a (large) sparse matrix by a pivot table.
UserId ...
1 5.0 NaN NaN NaN NaN NaN NaN NaN NaN NaN ...
2 NaN NaN NaN NaN NaN NaN NaN NaN NaN NaN ...
3 NaN NaN NaN NaN NaN NaN NaN NaN NaN NaN ...
4 NaN NaN NaN NaN NaN NaN NaN NaN NaN NaN ...
5 NaN NaN NaN NaN NaN 2.0 NaN NaN NaN NaN ...
... ... ... ... ... ... ... ... ... ... ... ...
6036 NaN NaN NaN 2.0 NaN 3.0 NaN NaN NaN NaN ...
6037 NaN NaN NaN NaN NaN NaN NaN NaN NaN NaN ...
6038 NaN NaN NaN NaN NaN NaN NaN NaN NaN NaN ...
6039 NaN NaN NaN NaN NaN NaN NaN NaN NaN NaN ...
6040 3.0 NaN NaN NaN NaN NaN NaN NaN NaN NaN ...
MovieId 3943 3944 3945 3946 3947 3948 3949 3950 3951 3952
UserId
1 NaN NaN NaN NaN NaN NaN NaN NaN NaN NaN
2 NaN NaN NaN NaN NaN NaN NaN NaN NaN NaN
3 NaN NaN NaN NaN NaN NaN NaN NaN NaN NaN
4 NaN NaN NaN NaN NaN NaN NaN NaN NaN NaN
5 NaN NaN NaN NaN NaN NaN NaN NaN NaN NaN
... ... ... ... ... ... ... ... ... ... ...
6036 NaN NaN NaN NaN NaN NaN NaN NaN NaN NaN
6037 NaN NaN NaN NaN NaN NaN NaN NaN NaN NaN
6038 NaN NaN NaN NaN NaN NaN NaN NaN NaN NaN
6039 NaN NaN NaN NaN NaN NaN NaN NaN NaN NaN
6040 NaN NaN NaN NaN NaN NaN NaN NaN NaN NaN
Now, I am looking for a way for, given a row index (e.g. 1) select all index whose values are > 4.0. Is there a simple way to do so?.
I tried the following
df.loc[1] >= 4.0
however what I get is
MovieId
1 True
2 False
3 False
4 False
5 False
...
3948 False
3949 False
3950 False
3951 False
3952 False
Name: 1, Length: 3706, dtype: bool
meaning I am almost there, but not quite. How do I extract the indices corresponding to True?
You can chain two loc selections, the first selects the rows based on label, the second will use a function to subset the columns based on your condition. Or you could use a single nested loc, where the columns mask also calls .loc
import numpy as np
import pandas as pd
np.random.seed(42)
df = pd.DataFrame(np.random.choice([1, np.NaN, 5], p=[.2, .7, .1], size=(2, 40)))
df.loc[1].loc[lambda x: x >= 4]
#or
df.loc[1, df.loc[1] >= 4]
#3 5.0
#10 5.0
#12 5.0
#15 5.0
#29 5.0
#Name: 1, dtype: float64
I forward fill values in the following df using:
df = (df.resample('d') # ensure data is daily time series
.ffill()
.sort_index(ascending=True))
df before forward fill
id a b c d
datadate
1980-01-31 NaN NaN NaN NaN
1980-02-29 NaN 2 NaN NaN
1980-03-31 NaN NaN NaN NaN
1980-04-30 1 NaN 3 4
1980-05-31 NaN NaN NaN NaN
... ... ... ...
2019-08-31 NaN NaN NaN NaN
2019-09-30 NaN NaN NaN NaN
2019-10-31 NaN NaN NaN NaN
2019-11-30 NaN NaN NaN NaN
2019-12-31 NaN NaN 20 33
However, I wish to only forward fill one year after (date is datetime) the last observation and then the remaining rows simply be NaN. I am not sure what is the best way to introduce this criteria in this task. Any help would be super!
Thanks
If I understand you correctly, you want to forward-fill the values on Dec 31, 2019 to the next year. Try this:
end_date = df.index.max()
new_end_date = end_date + pd.offsets.DateOffset(years=1)
new_index = df.index.append(pd.date_range(end_date, new_end_date, closed='right'))
df = df.reindex(new_index)
df.loc[end_date:, :] = df.loc[end_date:, :].ffill()
Result:
a b c d
1980-01-31 NaN NaN NaN NaN
1980-02-29 NaN 2.0 NaN NaN
1980-03-31 NaN NaN NaN NaN
1980-04-30 1.0 NaN 3.0 4.0
1980-05-31 NaN NaN NaN NaN
2019-08-31 NaN NaN NaN NaN
2019-09-30 NaN NaN NaN NaN
2019-10-31 NaN NaN NaN NaN
2019-11-30 NaN NaN NaN NaN
2019-12-31 NaN NaN 20.0 33.0
2020-01-01 NaN NaN 20.0 33.0
2020-01-02 NaN NaN 20.0 33.0
...
2020-12-31 NaN NaN 20.0 33.0
One solution is to forward fill using a limit parameter, but this wont handle the leap-year:
df.fillna(mehotd='ffill', limit=365)
The second solution is to define a more robust function to do the forward fill in the 1-year window:
from pandas.tseries.offsets import DateOffsets
def fun(serie_df):
serie = serie_df.copy()
indexes = serie[~serie.isnull()].index
for idx in indexes:
mask = (serie.index >= idx) & (serie.index < idx+DateOffset(years=1))
serie.loc[mask] = serie[mask].fillna(method='ffill')
return serie
df_filled = df.apply(fun, axis=0)
If a column has multiple non-nan values in the same 1-year window, then the first fill will stop once the most recent value is encounter. The second solution will treat the consecutive value as if they were independent.
I have a datetime indexed series like this:
2018-08-27 17:45:01 1
2018-08-27 16:01:12 1
2018-08-27 13:48:47 1
2018-08-26 22:26:40 2
2018-08-26 20:10:42 1
2018-08-26 18:20:32 1
2018-08-25 23:07:51 1
2018-08-25 01:46:08 1
2018-09-18 14:08:23 1
2018-09-17 19:38:38 1
2018-09-15 22:40:45 1
What is an elegant way to reformat this into a time indexed dataframe whose columns are dates? For example:
2018-10-24 2018-06-28 2018-10-23
15:16:41 1.0 NaN NaN
15:18:16 1.0 NaN NaN
15:21:42 1.0 NaN NaN
23:35:00 NaN NaN 1.0
23:53:13 NaN 1.0 NaN
Current approach:
time_date_dict = defaultdict(partial(defaultdict, int))
for i in series.iteritems():
datetime = i[0]
value = i[1]
time_date_dict[datetime.time()][datetime.date()] = value
time_date_df = pd.DataFrame.from_dict(time_date_dict, orient='index')
Use pivot:
df1 = pd.pivot(s.index.time, s.index.date, s)
#if want strings index and columns names
#df1 = pd.pivot(s.index.strftime('%H:%M:%S'), s.index.strftime('%Y-%m-%d'), s)
print (df1)
date 2018-08-25 2018-08-26 2018-08-27 2018-09-15 2018-09-17 \
date
01:46:08 1.0 NaN NaN NaN NaN
13:48:47 NaN NaN 1.0 NaN NaN
14:08:23 NaN NaN NaN NaN NaN
16:01:12 NaN NaN 1.0 NaN NaN
17:45:01 NaN NaN 1.0 NaN NaN
18:20:32 NaN 1.0 NaN NaN NaN
19:38:38 NaN NaN NaN NaN 1.0
20:10:42 NaN 1.0 NaN NaN NaN
22:26:40 NaN 2.0 NaN NaN NaN
22:40:45 NaN NaN NaN 1.0 NaN
23:07:51 1.0 NaN NaN NaN NaN
date 2018-09-18
date
01:46:08 NaN
13:48:47 NaN
14:08:23 1.0
16:01:12 NaN
17:45:01 NaN
18:20:32 NaN
19:38:38 NaN
20:10:42 NaN
22:26:40 NaN
22:40:45 NaN
23:07:51 NaN
I have the following strange error with pandas(pandas==0.23.1) :
import pandas as pd
df = pd.DataFrame({'t1': ["a","b","c"]*10000, 't2': ["x","y","z"]*10000, 'i1': list(range(5000))*6, 'i2': list(range(5000))*6, 'dummy':0})
# works fast with less memory
piv = df.pivot_table(values='dummy', index=['i1','i2'], columns=['t1','t2'])
d2 = df.copy()
d2.t1 = d2.t1.astype('category')
d2.t2 = d2.t2.astype('category')
# needs > 20GB of memory and takes for ever
piv2 = d2.pivot_table(values='dummy', index=['i1','i2'], columns=['t1','t2'])
I am wondering if this is expected and I am doing something wrong, or if this is a bug in pandas. Should dtype category for str not be very transparent (for this use case)?
This is not a bug. What's happening is pandas.pivot_table is calculating the Cartesian product of grouper categories.
This is a known intended behaviour. In Pandas v0.23.0, we saw the introduction of the observed argument for pandas.groupby. Setting observed=True only includes observed combinations; it is False by default. This argument has not yet now been rolled out to related methods such as pandas.pivot_table. In my opinion, it should be.
But now let's see what this means. We can use an example dataframe and see what happens when we print the result.
Setup
We make the dataframe substantially smaller:
import pandas as pd
n = 10
df = pd.DataFrame({'t1': ["a","b","c"]*n, 't2': ["x","y","z"]*n,
'i1': list(range(int(n/2)))*6, 'i2': list(range(int(n/2)))*6,
'dummy':0})
Without categories
This is likely what you are looking for. Unobserved combinations of categories are not represented in your pivot table.
piv = df.pivot_table(values='dummy', index=['i1','i2'], columns=['t1','t2'])
print(piv)
t1 a b c
t2 x y z
i1 i2
0 0 0 0 0
1 1 0 0 0
2 2 0 0 0
3 3 0 0 0
4 4 0 0 0
With categories
With categories, all combinations of categories, even unobserved combinations, are accounted for in the result. This is expensive computationally and memory-hungry. Moreover, the dataframe is dominated by NaN from unobserved combinations. It's probably not what you want.
Update: you can now set the observed parameter to True to only show observed values for categorical groupers.
d2 = df.copy()
d2.t1 = d2.t1.astype('category')
d2.t2 = d2.t2.astype('category')
piv2 = d2.pivot_table(values='dummy', index=['i1','i2'], columns=['t1','t2'])
print(piv2)
t1 a b c
t2 x y z x y z x y z
i1 i2
0 0 0.0 NaN NaN NaN 0.0 NaN NaN NaN 0.0
1 NaN NaN NaN NaN NaN NaN NaN NaN NaN
2 NaN NaN NaN NaN NaN NaN NaN NaN NaN
3 NaN NaN NaN NaN NaN NaN NaN NaN NaN
4 NaN NaN NaN NaN NaN NaN NaN NaN NaN
1 0 NaN NaN NaN NaN NaN NaN NaN NaN NaN
1 0.0 NaN NaN NaN 0.0 NaN NaN NaN 0.0
2 NaN NaN NaN NaN NaN NaN NaN NaN NaN
3 NaN NaN NaN NaN NaN NaN NaN NaN NaN
4 NaN NaN NaN NaN NaN NaN NaN NaN NaN
2 0 NaN NaN NaN NaN NaN NaN NaN NaN NaN
1 NaN NaN NaN NaN NaN NaN NaN NaN NaN
2 0.0 NaN NaN NaN 0.0 NaN NaN NaN 0.0
3 NaN NaN NaN NaN NaN NaN NaN NaN NaN
4 NaN NaN NaN NaN NaN NaN NaN NaN NaN
3 0 NaN NaN NaN NaN NaN NaN NaN NaN NaN
1 NaN NaN NaN NaN NaN NaN NaN NaN NaN
2 NaN NaN NaN NaN NaN NaN NaN NaN NaN
3 0.0 NaN NaN NaN 0.0 NaN NaN NaN 0.0
4 NaN NaN NaN NaN NaN NaN NaN NaN NaN
4 0 NaN NaN NaN NaN NaN NaN NaN NaN NaN
1 NaN NaN NaN NaN NaN NaN NaN NaN NaN
2 NaN NaN NaN NaN NaN NaN NaN NaN NaN
3 NaN NaN NaN NaN NaN NaN NaN NaN NaN
4 0.0 NaN NaN NaN 0.0 NaN NaN NaN 0.0
For certain columns of df, if 80% of the column is NAN.
What's the simplest code to drop such columns?
You can use isnull with mean for threshold and then remove columns by boolean indexing with loc (because remove columns), also need invert condition - so <.8 means remove all columns >=0.8:
df = df.loc[:, df.isnull().mean() < .8]
Sample:
np.random.seed(100)
df = pd.DataFrame(np.random.random((100,5)), columns=list('ABCDE'))
df.loc[:80, 'A'] = np.nan
df.loc[:5, 'C'] = np.nan
df.loc[20:, 'D'] = np.nan
print (df.isnull().mean())
A 0.81
B 0.00
C 0.06
D 0.80
E 0.00
dtype: float64
df = df.loc[:, df.isnull().mean() < .8]
print (df.head())
B C E
0 0.278369 NaN 0.004719
1 0.670749 NaN 0.575093
2 0.209202 NaN 0.219697
3 0.811683 NaN 0.274074
4 0.940030 NaN 0.175410
If want remove columns by minimal values dropna working nice with parameter thresh and axis=1 for remove columns:
np.random.seed(1997)
df = pd.DataFrame(np.random.choice([np.nan,1], p=(0.8,0.2),size=(10,10)))
print (df)
0 1 2 3 4 5 6 7 8 9
0 NaN NaN NaN 1.0 1.0 NaN NaN NaN NaN NaN
1 1.0 NaN 1.0 NaN NaN NaN NaN NaN NaN NaN
2 NaN NaN NaN NaN NaN 1.0 1.0 NaN NaN NaN
3 NaN NaN NaN NaN 1.0 NaN NaN NaN NaN NaN
4 NaN NaN NaN NaN NaN 1.0 NaN NaN NaN 1.0
5 NaN NaN NaN 1.0 1.0 NaN NaN 1.0 NaN 1.0
6 NaN NaN NaN NaN NaN NaN NaN NaN NaN NaN
7 NaN NaN NaN NaN NaN NaN NaN NaN NaN NaN
8 NaN NaN NaN NaN NaN NaN NaN 1.0 NaN NaN
9 1.0 NaN NaN NaN 1.0 NaN NaN 1.0 NaN NaN
df1 = df.dropna(thresh=2, axis=1)
print (df1)
0 3 4 5 7 9
0 NaN 1.0 1.0 NaN NaN NaN
1 1.0 NaN NaN NaN NaN NaN
2 NaN NaN NaN 1.0 NaN NaN
3 NaN NaN 1.0 NaN NaN NaN
4 NaN NaN NaN 1.0 NaN 1.0
5 NaN 1.0 1.0 NaN 1.0 1.0
6 NaN NaN NaN NaN NaN NaN
7 NaN NaN NaN NaN NaN NaN
8 NaN NaN NaN NaN 1.0 NaN
9 1.0 NaN 1.0 NaN 1.0 NaN
EDIT: For non-Boolean data
Total number of NaN entries in a column must be less than 80% of total entries:
df = df.loc[:, df.isnull().sum() < 0.8*df.shape[0]]
df.dropna(thresh=np.int((100-percent_NA_cols_required)*(len(df.columns)/100)),inplace=True)
Basically pd.dropna takes number(int) of non_na cols required if that row is to be removed.
You can use the pandas dropna. For example:
df.dropna(axis=1, thresh = int(0.2*df.shape[0]), inplace=True)
Notice that we used 0.2 which is 1-0.8 since the thresh refers to the number of non-NA values
As suggested in comments, if you use sum() on a boolean test, you can get the number of occurences.
Code:
def get_nan_cols(df, nan_percent=0.8):
threshold = len(df.index) * nan_percent
return [c for c in df.columns if sum(df[c].isnull()) >= threshold]
Used as:
del df[get_nan_cols(df, 0.8)]