# ICE Revision: $Id$
"""Extended version of the Pandas-Dataframe
"""
from pandas import DataFrame,Series
from numpy import hstack,unique
from math import isnan
from PyFoam.Error import error,warning,PyFoamException
from PyFoam.ThirdParty.six import string_types,text_type,u
[docs]class PyFoamDataFrame(DataFrame):
"""This class adds some convenience functions to the regular Datafram class"""
validOtherTypes=(DataFrame,Series)
[docs] def __init__(self,*args,**kwargs):
"""Adds no data. Just passes the arguments to the super-class"""
super(PyFoamDataFrame,self).__init__(*args,**kwargs)
if not self.__allStrings():
raise PandasWrapperPyFoamException("Columns must be strings")
# if not self.axes[0].is_unique:
# # not working as expected
# # self.drop_duplicates(inplace=True)
# self.axes[0].is_unique=True
def __allStrings(self,keys=None):
if keys is None:
keys=self.keys()
return keys.map(lambda k:isinstance(k,string_types)).all()
[docs] def addData(self,other,
sameIndex=True,
mergeIndex=False,
prefix=None,
suffix=None,
allowExtrapolate=False,
interpolationMethod="values"):
"""Add data from another DataFrame or Series
:param other: data as Pandas-DataFrame or Series
:param sameIndex: assum both have the same indices. If False the other data will be interpolated to the current indices
:param mergeIndex: make the result indices a mixture of the indices"""
if not sameIndex and mergeIndex:
raise PandasWrapperPyFoamException("Can't specify sameIndex=False and mergeIndex=True at the same time")
if not isinstance(other,self.validOtherTypes):
raise PandasWrapperPyFoamException("Other data is of type",type(other),
"should be one of",self.validOtherTypes)
if isinstance(other,DataFrame):
o=other
else:
o=DataFrame(other)
k=o.keys()
if not self.__allStrings(k):
raise PandasWrapperPyFoamException("Added data with non-string columns")
v=k.copy()
if prefix:
v=[prefix+n for n in v]
if suffix:
v=[n+suffix for n in v]
if len(set(v)&set(self.keys()))>0:
raise PandasWrapperPyFoamException("Keys of this",self.keys(),"and other",v,
"intersect",set(v)&set(self.keys()))
keys=dict(zip(k,v))
interpolate=False # only interpolate if necessary
if len(self.index)!=len(o.index) or (self.index!=o.index).any():
if sameIndex and not mergeIndex:
raise PandasWrapperPyFoamException("Other data has different index. Specify sameIndex=False or mergeIndex=True")
ni=unique(hstack([self.index,o.index]))
interpolate=True
if mergeIndex:
minOld=min(self.index)
maxOld=max(self.index)
result=self.reindex(index=ni,copy=True).interpolate(
method=interpolationMethod,limit=1)
if not allowExtrapolate:
for s in result:
result[s][result.index<minOld]=float("NaN")
result[s][result.index>maxOld]=float("NaN")
else:
# make sure we have values at the current position
# o=o.reindex_axis(ni,axis='index').interpolate(method=interpolationMethod)
o=o.reindex(index=ni,columns=o.columns).interpolate(method=interpolationMethod)
# ,takeable=True
result=self.copy()
else:
result=self.copy()
minOld=min(o.index)
maxOld=max(o.index)
for k,v in keys.items():
result[v]=o[k]
if interpolate:
result[v]=result[v].interpolate(method=interpolationMethod,limit=1)
if not allowExtrapolate:
result[v][result.index<minOld]=float("NaN")
result[v][result.index>maxOld]=float("NaN")
return PyFoamDataFrame(result)
[docs] def integrate(self,columns=None):
"""Integrate by using the trapezoid rule. Return a dictionary with values.
:param values: list of column names. If unset all are integrated"""
return self.__integrateInternal(columns)[0]
[docs] def validLength(self,columns=None):
"""Length were the values are valid (not NaN) Return a dictionary with values.
:param values: list of column names. If unset all are integrated"""
return self.__integrateInternal(columns)[1]
[docs] def weightedAverage(self,columns=None):
"""Weighted average. Return a dictionary with values.
:param values: list of column names. If unset all are integrated"""
integral,length=self.__integrateInternal(columns)
result={}
for k in integral:
if length[k]>0 and not isnan(length[k]):
result[k]=integral[k]/length[k]
else:
result[k]=float("NaN")
return result
def __integrateInternal(self,columns):
if columns is None:
columns=self.keys()
integrals={}
lengths={}
ind=self.index
for k in columns:
integrals[k]=0
lengths[k]=0
if len(ind)<2: # no weighting possible
integrals[k]=float("NaN")
continue
val=self[k].values
for i in range(len(ind)):
if not isnan(val[i]):
w=0
if i>0:
w+=0.5*(ind[i]-ind[i-1])
if i+1<len(ind):
w+=0.5*(ind[i+1]-ind[i])
lengths[k]+=w
integrals[k]+=w*val[i]
if lengths[k]==0:
integrals[k]=float("NaN")
return integrals,lengths
[docs] def describe(self,*args,**kwargs):
"""Adds our own statistics to the regular describe"""
d=super(PyFoamDataFrame,self).describe(*args,**kwargs)
integral,length=self.__integrateInternal(self.keys())
d=d.append(DataFrame(data=integral,index=["integral"]))
d=d.append(DataFrame(data=length,index=["valid length"]))
a={}
for k in integral:
if length[k]>0 and not isnan(length[k]):
a[k]=integral[k]/length[k]
else:
a[k]=float("NaN")
d=d.append(DataFrame(data=a,index=["weighted average"]))
return d
[docs]class PandasWrapperPyFoamException(PyFoamException):
"""The PyFoam-exception that does not expect to be caught"""
[docs] def __init__(self,*text):
descr="Problem in wrapper to pandas-library"
# super(FatalErrorPyFoamException,self).__init__(descr,*text) # does not work with Python 2.4
PyFoamException.__init__(self,descr,*text)