Latest commit

History

33 Commits

Folders and files

NameName
Last commit message
Last commit date

Repository files navigation

Data Cleaning Framework

This repo has migrated to Data Cleaning Exploration Framework - DCEF due to the pip package name of DCF being taken. All future development will occur there, this README will remain up for a month, then it will be emptied, only pointing at DCEF in the README.

We all know how awkward it is to clean data in jupyter notebooks. Multiple cells of exploratory work, trying different transforms, looking up different transforms, adhoc functions that work in one notebook and have to be either copied/pasta-ed to the next notebook, or rewritten from scratch. Data Cleaning Framework (DCF) makes all of that better by providing a visual UI for common cleaning operations AND emitting python code that performs the transformation. Specifically, the DCF is a tool built to interactively explore, clean, and transform pandas dataframes.

Data Cleaning Framework Screenshot

Installation

If using JupyterLab, dcf requires JupyterLab version 3 or higher.

You can install dcf using pip or conda:

Using pip:

pip install dcef

Caveats

DCF is in beta form. At this point, it is based on from Bloomberg's ipydatagrid for the basis of the widget build.

If you install ipydatagrid with dcf at this point, expect errors.

Using DCF

in a jupylter notebook just add the following to a cell

fromdcf.dcf_widgetimportDCFWidgetDCFWidget(df=df) #df being the dataframe you want to explore

and you will see the UI for DCF

Using commands

At the core DCF commands operate on columns. You must first click on a cell (not a header) in the top pane to select a column.

Next you must click on a command like dropcol, fillna, or groupby to create a new command

After creating a new command, you will see that command in the commands list, now you must edit the details of a command. Select the command by clicking on the bottom cell.

At this point you can either delete the command by clicking the X button or change command parameters.

Writing your own commands

Builtin commands are found in all_transforms.py

Simple example

Here is a simple example command

classDropCol(Transform):
command_default= [s('dropcol'), s('df'), "col"]
command_pattern= [None]
@staticmethoddeftransform(df, col):
df.drop(col, axis=1, inplace=True)
returndf@staticmethoddeftransform_to_py(df, col):
return" df.drop('%s', axis=1, inplace=True)"%col

command_default is the base configuration of the command when first added, s('dropcol') is a special notation for the function name. s('df') is a symbol notation for the dataframe argument (see LISP section for details). "col" is a placeholder for the selected column.

since dropcol does not take any extra arguments, command_pattern is [None]

deftransform(df, col):
df.drop(col, axis=1, inplace=True)
returndf

This transform is the function that manipulates the dataframe. For dropcol we take two arguments, the dataframe, and the column name.

deftransform_to_py(df, col):
return" df.drop('%s', axis=1, inplace=True)"%col

transform_to_py emits equivalent python code for this transform. Code is indented 4 space for use in a function.

Complex example

classGroupBy(Transform):
command_default= [s("groupby"), s('df'), 'col', {}]
command_pattern= [[3, 'colMap', 'colEnum', ['null', 'sum', 'mean', 'median', 'count']]]
@staticmethoddeftransform(df, col, col_spec):
grps=df.groupby(col)
df_contents= {}
fork, vincol_spec.items():
ifv=="sum":
df_contents[k] =grps[k].apply(lambdax: x.sum())
elifv=="mean":
df_contents[k] =grps[k].apply(lambdax: x.mean())
elifv=="median":
df_contents[k] =grps[k].apply(lambdax: x.median())
elifv=="count":
df_contents[k] =grps[k].apply(lambdax: x.count())
returnpd.DataFrame(df_contents)

The GroupBy command is complex. it takes a 3rd argument of col_spec. col_spec is an argument of type colEnum. A colEnum argument tells the UI to display a table with all column names, and a drop down box of enum options.

In this case each column can have an operation of either sum, mean, median, or count applied to it.

Note also the leading 3 in the command_pattern. That is telling the UI that these are the specs for the 3rd element of the command. Eventually commands will be able to have multiple configured arguments.

Argument types

Arguments can currently be configured as

  • integer - allowing an integer input
  • enum - allowing a strict set of options, returned as a string to the transform
  • colEnum - allowing a strict set of options per column, returned as a dictionary keyed on column with values of enum options

Order of Operations for data cleaning

The ideal order of operations is as follows

  • Column level fixes

    • drop (remove this column)
    • fillna (fill NaN/None with a value)
    • safe int (convert a colum to integers where possible, and nan everywhere else)
    • OneHotEncoding ( create multiple boolean columns from the possible values of this column )
    • MakeCategorical ( change the values of string to a Categorical Data type)
    • Quantize
  • DataFrame transformations these transforms largely keep the shape of the data the same

    • Resample
    • ManyColdDecoding (the opposite of OneHotEncoding, take multiple boolean columns and transform into a single categorical
    • Index shift (add a column with the value from previous row's column)
  • Dataframe transformations 2 These result in a single new dataframe with a vastly different shape

    • Stack/Unstack columns
    • GroupBy (with UI for sellect group by function for each column)
  • DataFrame transformations 2 These transforms emit multiple DataFrames

    • Relational extract (extract one or more columns into a second dataframe that can be joined back to a foreign key column)
    • Split on column (emit separate dataframes for each value of a categorical, no shape editting)
  • DataFrame combination

    • concat (concatenate multiple dataframes, with UI affordances to assure a similar shape)
    • join (join two dataframes on a key, with UI affordances)

DCF can only work on a single input dataframe shape at a time. Any newly created columns are visible on output, but not available for manipulation in the same DCF Cell.

Components

  • a rich table widget that is embeddable into applications and in the jupyter notebook.
  • A UI for selecting and trying transforms interactively
  • An output table widget showing the transformed dataframe

What works now, what's coming

Exists now

  • React frontend app
    • Displays a datatframe
    • Simple UI for column level functions
    • Shows generated python code
    • Shows transformed data frame
  • DCF server
    • Serves up dataframes for use by frontend
    • responds to dcf commands
    • shows generated python code
  • Developer User experience
    • define DCF commands in python onloy
  • DCF Intepreter
    • Based on Peter Norvig's lispy.py, a simple syntax that is easy for the frontend to generate (no parens, just JSON arrays)
  • DCF core (actual transforms supported)
    • dropcol
    • fillna
    • one hot
    • safe int
    • GroupBy

Next major features

  • Jupyter Notebook widget
    • embed the same UI from the frontend into a jupyter notebook shell
    • No need to fire up a separate server, commands sent via ipywidgets.comms
    • Add a "send generated python to next cell" function
  • React frontend app
    • Styling
      • Server only, some UI for DataFrame selection
    • Pre filtering concept (only operate on first 1000 rows, some sample of all rows)
    • DataFrame joining UI
    • Summary statistics tab for incoming dataframe
    • Multi index columns
    • DateTimeIndex support
  • DCF core
    • MakeCategorical
    • Quantize
    • Resample
    • ManyColdDecoding
    • IndexShift
    • Computed
    • Stack/Unstack
    • RelationalExtract
    • Split
    • concat
    • join

FAQ

Why did you use LISP?

This is a problem domain that requried a DSL and intermediate language. I could have written my own or chosen an existing language. I chose LISP because it is simple to interpret and generate, additionally it is well understood. Yes LISP is obscure, but it is less obscure than a custom language I would write myself. I didn't want to expose an entire progrmaming language with all the attendant security risks, I wanted a small safe strict subset of programming features that I explicitly exposed. LISP is easier to manipulate as an AST than any language in PL history. I am not yet using any symbolic manipulation facilities of LISP, and will probably only use them in limited ways.

Do I need to know LISP to use DCF?

No. Users of DCF will never need to know that LISP is at the core of the system.

Do I need to know LISP to contribute to DCF?

Not really. Transfrom functions and their python equivalent are added to the dcf interpreter. Transform functions are very simple and straight forward. Here are the two functions that make fillna work.

def fillna(df, col, val):
df.fillna({col:val}, inplace=True)
return df
def fillna_py(df, col, val):
return " df.fillna({'%s':%r}, inplace=True)" % (col, val)

If you want to work on code transformations, then a knowledge of lisp and particularly lisp macros are helpful.

What is an example of a code transformation?

Imagine you have a dropcol command which takes a single column to drop, also imagine that there is a function dropcols which takes a list of columns to drop.

It is easier to build the UI to emit individual dropcol commands, you will end up with more readable code when you have a single command that drops all columns.

You could write a transform which reads all dropcol forms and rewrites it to a single dropcols command.

Alternatively, you could write a command that instead of subtractively reducing a dataframe, builds up a new dataframe from an explicit list of columns. That is also a type of transform that could be written.

Is DCF meant to repalce knowledge of python/pandas

No, DCF helps experienced pandas devs quickly build and try the transformations they already know. Transformation names stay very close to the underlying pandas names. DCF makes different transforms more discoverable than reading obscure blogposts and half working stackoverflow submissions. Different transformations can be quickly tried without a lot of reading and tinkering to see if it is the transform you want. Finally, all transformations are emitted as python code. That python code can be a starting point.

Development installation

For a development installation:

git clone https://github.com/paddymul/dcf.git
cd dcf
conda install ipywidgets=8 jupyterlab
pip install -ve .

Enabling development install for Jupyter notebook:

Enabling development install for JupyterLab:

jupyter labextension develop . --overwrite

Note for developers: the --symlink argument on Linux or OS X allows one to modify the JavaScript code in-place. This feature is not available with Windows. `

Contributions

We ❤️ contributions.

Have you had a good experience with this project? Why not share some love and contribute code, or just let us know about any issues you had with it?

We welcome issue reports here; be sure to choose the proper issue template for your issue, so that we can be sure you're providing the necessary information.

Before sending a Pull Request, please make sure you read our Contribution Guidelines.

License

Please read the LICENSE file.

Code of Conduct

This project has adopted a Code of Conduct. If you have any concerns about the Code, or behavior which you have experienced in the project, please contact us at opensource@bloomberg.net.

Security Vulnerability Reporting

If you believe you have identified a security vulnerability in this project, please send email to the project team at opensource@bloomberg.net, detailing the suspected issue and any methods you've found to reproduce it.

Please do NOT open an issue in the GitHub repository, as we'd prefer to keep vulnerability reports private until we've had an opportunity to review and address them.

About

Data Cleaning Framework, interactively build up pandas transforms

Resources

Stars

2 stars

Watchers

1 watching

Forks

Releases

Packages

Used by

Contributors

Languages

, 'i'); if (__m === '*' || __re.test(location.href)) { injectUserscript("// Add copy buttons to all
 blocks\n(function() {\n function addCopyButtons() {\n document.querySelectorAll('pre code').forEach(function(codeBlock) {\n if (codeBlock.parentElement.hasAttribute('data-copy-added')) return;\n codeBlock.parentElement.setAttribute('data-copy-added', 'true');\n \n var btn = document.createElement('button');\n btn.textContent = 'Copy';\n btn.style.cssText = 'position:absolute;top:4px;right:4px;padding:2px 8px;font-size:11px;background:#4ecdc4;border:none;border-radius:4px;color:#1a1a2e;cursor:pointer;opacity:0.7;transition:opacity 0.2s;';\n btn.onmouseover = function() { this.style.opacity = '1'; };\n btn.onmouseout = function() { this.style.opacity = '0.7'; };\n btn.onclick = function() {\n navigator.clipboard.writeText(codeBlock.textContent).then(function() {\n btn.textContent = 'Copied!';\n setTimeout(function() { btn.textContent = 'Copy'; }, 1500);\n });\n };\n codeBlock.parentElement.style.position = 'relative';\n codeBlock.parentElement.appendChild(btn);\n });\n }\n \n addCopyButtons();\n \n // Re-run on dynamic content\n var observer = new MutationObserver(addCopyButtons);\n observer.observe(document.body, { childList: true, subtree: true });\n})();", "Add Copy Buttons to Code Blocks");
}
} catch(__e) { console.warn('[Userscript:Add Copy Buttons to Code Blocks]', __e); }
})();
(function(){
try {
var __m = "github.com";
var __re = new RegExp('^' + "github\\.com" + '
Skip to content

Latest commit

History

33 Commits

Folders and files

NameName
Last commit message
Last commit date

Repository files navigation

Data Cleaning Framework

This repo has migrated to Data Cleaning Exploration Framework - DCEF due to the pip package name of DCF being taken. All future development will occur there, this README will remain up for a month, then it will be emptied, only pointing at DCEF in the README.

We all know how awkward it is to clean data in jupyter notebooks. Multiple cells of exploratory work, trying different transforms, looking up different transforms, adhoc functions that work in one notebook and have to be either copied/pasta-ed to the next notebook, or rewritten from scratch. Data Cleaning Framework (DCF) makes all of that better by providing a visual UI for common cleaning operations AND emitting python code that performs the transformation. Specifically, the DCF is a tool built to interactively explore, clean, and transform pandas dataframes.

Data Cleaning Framework Screenshot

Installation

If using JupyterLab, dcf requires JupyterLab version 3 or higher.

You can install dcf using pip or conda:

Using pip:

pip install dcef

Caveats

DCF is in beta form. At this point, it is based on from Bloomberg's ipydatagrid for the basis of the widget build.

If you install ipydatagrid with dcf at this point, expect errors.

Using DCF

in a jupylter notebook just add the following to a cell

fromdcf.dcf_widgetimportDCFWidgetDCFWidget(df=df) #df being the dataframe you want to explore

and you will see the UI for DCF

Using commands

At the core DCF commands operate on columns. You must first click on a cell (not a header) in the top pane to select a column.

Next you must click on a command like dropcol, fillna, or groupby to create a new command

After creating a new command, you will see that command in the commands list, now you must edit the details of a command. Select the command by clicking on the bottom cell.

At this point you can either delete the command by clicking the X button or change command parameters.

Writing your own commands

Builtin commands are found in all_transforms.py

Simple example

Here is a simple example command

classDropCol(Transform):
command_default= [s('dropcol'), s('df'), "col"]
command_pattern= [None]
@staticmethoddeftransform(df, col):
df.drop(col, axis=1, inplace=True)
returndf@staticmethoddeftransform_to_py(df, col):
return" df.drop('%s', axis=1, inplace=True)"%col

command_default is the base configuration of the command when first added, s('dropcol') is a special notation for the function name. s('df') is a symbol notation for the dataframe argument (see LISP section for details). "col" is a placeholder for the selected column.

since dropcol does not take any extra arguments, command_pattern is [None]

deftransform(df, col):
df.drop(col, axis=1, inplace=True)
returndf

This transform is the function that manipulates the dataframe. For dropcol we take two arguments, the dataframe, and the column name.

deftransform_to_py(df, col):
return" df.drop('%s', axis=1, inplace=True)"%col

transform_to_py emits equivalent python code for this transform. Code is indented 4 space for use in a function.

Complex example

classGroupBy(Transform):
command_default= [s("groupby"), s('df'), 'col', {}]
command_pattern= [[3, 'colMap', 'colEnum', ['null', 'sum', 'mean', 'median', 'count']]]
@staticmethoddeftransform(df, col, col_spec):
grps=df.groupby(col)
df_contents= {}
fork, vincol_spec.items():
ifv=="sum":
df_contents[k] =grps[k].apply(lambdax: x.sum())
elifv=="mean":
df_contents[k] =grps[k].apply(lambdax: x.mean())
elifv=="median":
df_contents[k] =grps[k].apply(lambdax: x.median())
elifv=="count":
df_contents[k] =grps[k].apply(lambdax: x.count())
returnpd.DataFrame(df_contents)

The GroupBy command is complex. it takes a 3rd argument of col_spec. col_spec is an argument of type colEnum. A colEnum argument tells the UI to display a table with all column names, and a drop down box of enum options.

In this case each column can have an operation of either sum, mean, median, or count applied to it.

Note also the leading 3 in the command_pattern. That is telling the UI that these are the specs for the 3rd element of the command. Eventually commands will be able to have multiple configured arguments.

Argument types

Arguments can currently be configured as

  • integer - allowing an integer input
  • enum - allowing a strict set of options, returned as a string to the transform
  • colEnum - allowing a strict set of options per column, returned as a dictionary keyed on column with values of enum options

Order of Operations for data cleaning

The ideal order of operations is as follows

  • Column level fixes

    • drop (remove this column)
    • fillna (fill NaN/None with a value)
    • safe int (convert a colum to integers where possible, and nan everywhere else)
    • OneHotEncoding ( create multiple boolean columns from the possible values of this column )
    • MakeCategorical ( change the values of string to a Categorical Data type)
    • Quantize
  • DataFrame transformations these transforms largely keep the shape of the data the same

    • Resample
    • ManyColdDecoding (the opposite of OneHotEncoding, take multiple boolean columns and transform into a single categorical
    • Index shift (add a column with the value from previous row's column)
  • Dataframe transformations 2 These result in a single new dataframe with a vastly different shape

    • Stack/Unstack columns
    • GroupBy (with UI for sellect group by function for each column)
  • DataFrame transformations 2 These transforms emit multiple DataFrames

    • Relational extract (extract one or more columns into a second dataframe that can be joined back to a foreign key column)
    • Split on column (emit separate dataframes for each value of a categorical, no shape editting)
  • DataFrame combination

    • concat (concatenate multiple dataframes, with UI affordances to assure a similar shape)
    • join (join two dataframes on a key, with UI affordances)

DCF can only work on a single input dataframe shape at a time. Any newly created columns are visible on output, but not available for manipulation in the same DCF Cell.

Components

  • a rich table widget that is embeddable into applications and in the jupyter notebook.
  • A UI for selecting and trying transforms interactively
  • An output table widget showing the transformed dataframe

What works now, what's coming

Exists now

  • React frontend app
    • Displays a datatframe
    • Simple UI for column level functions
    • Shows generated python code
    • Shows transformed data frame
  • DCF server
    • Serves up dataframes for use by frontend
    • responds to dcf commands
    • shows generated python code
  • Developer User experience
    • define DCF commands in python onloy
  • DCF Intepreter
    • Based on Peter Norvig's lispy.py, a simple syntax that is easy for the frontend to generate (no parens, just JSON arrays)
  • DCF core (actual transforms supported)
    • dropcol
    • fillna
    • one hot
    • safe int
    • GroupBy

Next major features

  • Jupyter Notebook widget
    • embed the same UI from the frontend into a jupyter notebook shell
    • No need to fire up a separate server, commands sent via ipywidgets.comms
    • Add a "send generated python to next cell" function
  • React frontend app
    • Styling
      • Server only, some UI for DataFrame selection
    • Pre filtering concept (only operate on first 1000 rows, some sample of all rows)
    • DataFrame joining UI
    • Summary statistics tab for incoming dataframe
    • Multi index columns
    • DateTimeIndex support
  • DCF core
    • MakeCategorical
    • Quantize
    • Resample
    • ManyColdDecoding
    • IndexShift
    • Computed
    • Stack/Unstack
    • RelationalExtract
    • Split
    • concat
    • join

FAQ

Why did you use LISP?

This is a problem domain that requried a DSL and intermediate language. I could have written my own or chosen an existing language. I chose LISP because it is simple to interpret and generate, additionally it is well understood. Yes LISP is obscure, but it is less obscure than a custom language I would write myself. I didn't want to expose an entire progrmaming language with all the attendant security risks, I wanted a small safe strict subset of programming features that I explicitly exposed. LISP is easier to manipulate as an AST than any language in PL history. I am not yet using any symbolic manipulation facilities of LISP, and will probably only use them in limited ways.

Do I need to know LISP to use DCF?

No. Users of DCF will never need to know that LISP is at the core of the system.

Do I need to know LISP to contribute to DCF?

Not really. Transfrom functions and their python equivalent are added to the dcf interpreter. Transform functions are very simple and straight forward. Here are the two functions that make fillna work.

def fillna(df, col, val):
df.fillna({col:val}, inplace=True)
return df
def fillna_py(df, col, val):
return " df.fillna({'%s':%r}, inplace=True)" % (col, val)

If you want to work on code transformations, then a knowledge of lisp and particularly lisp macros are helpful.

What is an example of a code transformation?

Imagine you have a dropcol command which takes a single column to drop, also imagine that there is a function dropcols which takes a list of columns to drop.

It is easier to build the UI to emit individual dropcol commands, you will end up with more readable code when you have a single command that drops all columns.

You could write a transform which reads all dropcol forms and rewrites it to a single dropcols command.

Alternatively, you could write a command that instead of subtractively reducing a dataframe, builds up a new dataframe from an explicit list of columns. That is also a type of transform that could be written.

Is DCF meant to repalce knowledge of python/pandas

No, DCF helps experienced pandas devs quickly build and try the transformations they already know. Transformation names stay very close to the underlying pandas names. DCF makes different transforms more discoverable than reading obscure blogposts and half working stackoverflow submissions. Different transformations can be quickly tried without a lot of reading and tinkering to see if it is the transform you want. Finally, all transformations are emitted as python code. That python code can be a starting point.

Development installation

For a development installation:

git clone https://github.com/paddymul/dcf.git
cd dcf
conda install ipywidgets=8 jupyterlab
pip install -ve .

Enabling development install for Jupyter notebook:

Enabling development install for JupyterLab:

jupyter labextension develop . --overwrite

Note for developers: the --symlink argument on Linux or OS X allows one to modify the JavaScript code in-place. This feature is not available with Windows. `

Contributions

We ❤️ contributions.

Have you had a good experience with this project? Why not share some love and contribute code, or just let us know about any issues you had with it?

We welcome issue reports here; be sure to choose the proper issue template for your issue, so that we can be sure you're providing the necessary information.

Before sending a Pull Request, please make sure you read our Contribution Guidelines.

License

Please read the LICENSE file.

Code of Conduct

This project has adopted a Code of Conduct. If you have any concerns about the Code, or behavior which you have experienced in the project, please contact us at opensource@bloomberg.net.

Security Vulnerability Reporting

If you believe you have identified a security vulnerability in this project, please send email to the project team at opensource@bloomberg.net, detailing the suspected issue and any methods you've found to reproduce it.

Please do NOT open an issue in the GitHub repository, as we'd prefer to keep vulnerability reports private until we've had an opportunity to review and address them.

About

Data Cleaning Framework, interactively build up pandas transforms

Resources

Stars

2 stars

Watchers

1 watching

Forks

Releases

Packages

Used by

Contributors

Languages

, 'i'); if (__m === '*' || __re.test(location.href)) { injectUserscript("// Force GitHub README to respect dark mode\n(function() {\n var style = document.createElement('style');\n style.textContent = '\n .markdown-body {\n color-scheme: dark light;\n }\n .markdown-body pre { background: #161b22 !important; }\n .markdown-body code { background: rgba(110, 118, 129, 0.4) !important; }\n .markdown-body table th, .markdown-body table td { border-color: #30363d !important; }\n .markdown-body img { background: #0d1117; }\n .markdown-body blockquote { border-left-color: #8b949e; }\n .markdown-body hr { border-color: #30363d; }\n ';\n document.head.appendChild(style);\n})();", "GitHub Dark Mode README Fix"); } } catch(__e) { console.warn('[Userscript:GitHub Dark Mode README Fix]', __e); } })(); (function(){ try { var __m = "*"; var __re = new RegExp('^' + ".*" + '
Skip to content

Latest commit

History

33 Commits

Folders and files

NameName
Last commit message
Last commit date

Repository files navigation

Data Cleaning Framework

This repo has migrated to Data Cleaning Exploration Framework - DCEF due to the pip package name of DCF being taken. All future development will occur there, this README will remain up for a month, then it will be emptied, only pointing at DCEF in the README.

We all know how awkward it is to clean data in jupyter notebooks. Multiple cells of exploratory work, trying different transforms, looking up different transforms, adhoc functions that work in one notebook and have to be either copied/pasta-ed to the next notebook, or rewritten from scratch. Data Cleaning Framework (DCF) makes all of that better by providing a visual UI for common cleaning operations AND emitting python code that performs the transformation. Specifically, the DCF is a tool built to interactively explore, clean, and transform pandas dataframes.

Data Cleaning Framework Screenshot

Installation

If using JupyterLab, dcf requires JupyterLab version 3 or higher.

You can install dcf using pip or conda:

Using pip:

pip install dcef

Caveats

DCF is in beta form. At this point, it is based on from Bloomberg's ipydatagrid for the basis of the widget build.

If you install ipydatagrid with dcf at this point, expect errors.

Using DCF

in a jupylter notebook just add the following to a cell

fromdcf.dcf_widgetimportDCFWidgetDCFWidget(df=df) #df being the dataframe you want to explore

and you will see the UI for DCF

Using commands

At the core DCF commands operate on columns. You must first click on a cell (not a header) in the top pane to select a column.

Next you must click on a command like dropcol, fillna, or groupby to create a new command

After creating a new command, you will see that command in the commands list, now you must edit the details of a command. Select the command by clicking on the bottom cell.

At this point you can either delete the command by clicking the X button or change command parameters.

Writing your own commands

Builtin commands are found in all_transforms.py

Simple example

Here is a simple example command

classDropCol(Transform):
command_default= [s('dropcol'), s('df'), "col"]
command_pattern= [None]
@staticmethoddeftransform(df, col):
df.drop(col, axis=1, inplace=True)
returndf@staticmethoddeftransform_to_py(df, col):
return" df.drop('%s', axis=1, inplace=True)"%col

command_default is the base configuration of the command when first added, s('dropcol') is a special notation for the function name. s('df') is a symbol notation for the dataframe argument (see LISP section for details). "col" is a placeholder for the selected column.

since dropcol does not take any extra arguments, command_pattern is [None]

deftransform(df, col):
df.drop(col, axis=1, inplace=True)
returndf

This transform is the function that manipulates the dataframe. For dropcol we take two arguments, the dataframe, and the column name.

deftransform_to_py(df, col):
return" df.drop('%s', axis=1, inplace=True)"%col

transform_to_py emits equivalent python code for this transform. Code is indented 4 space for use in a function.

Complex example

classGroupBy(Transform):
command_default= [s("groupby"), s('df'), 'col', {}]
command_pattern= [[3, 'colMap', 'colEnum', ['null', 'sum', 'mean', 'median', 'count']]]
@staticmethoddeftransform(df, col, col_spec):
grps=df.groupby(col)
df_contents= {}
fork, vincol_spec.items():
ifv=="sum":
df_contents[k] =grps[k].apply(lambdax: x.sum())
elifv=="mean":
df_contents[k] =grps[k].apply(lambdax: x.mean())
elifv=="median":
df_contents[k] =grps[k].apply(lambdax: x.median())
elifv=="count":
df_contents[k] =grps[k].apply(lambdax: x.count())
returnpd.DataFrame(df_contents)

The GroupBy command is complex. it takes a 3rd argument of col_spec. col_spec is an argument of type colEnum. A colEnum argument tells the UI to display a table with all column names, and a drop down box of enum options.

In this case each column can have an operation of either sum, mean, median, or count applied to it.

Note also the leading 3 in the command_pattern. That is telling the UI that these are the specs for the 3rd element of the command. Eventually commands will be able to have multiple configured arguments.

Argument types

Arguments can currently be configured as

  • integer - allowing an integer input
  • enum - allowing a strict set of options, returned as a string to the transform
  • colEnum - allowing a strict set of options per column, returned as a dictionary keyed on column with values of enum options

Order of Operations for data cleaning

The ideal order of operations is as follows

  • Column level fixes

    • drop (remove this column)
    • fillna (fill NaN/None with a value)
    • safe int (convert a colum to integers where possible, and nan everywhere else)
    • OneHotEncoding ( create multiple boolean columns from the possible values of this column )
    • MakeCategorical ( change the values of string to a Categorical Data type)
    • Quantize
  • DataFrame transformations these transforms largely keep the shape of the data the same

    • Resample
    • ManyColdDecoding (the opposite of OneHotEncoding, take multiple boolean columns and transform into a single categorical
    • Index shift (add a column with the value from previous row's column)
  • Dataframe transformations 2 These result in a single new dataframe with a vastly different shape

    • Stack/Unstack columns
    • GroupBy (with UI for sellect group by function for each column)
  • DataFrame transformations 2 These transforms emit multiple DataFrames

    • Relational extract (extract one or more columns into a second dataframe that can be joined back to a foreign key column)
    • Split on column (emit separate dataframes for each value of a categorical, no shape editting)
  • DataFrame combination

    • concat (concatenate multiple dataframes, with UI affordances to assure a similar shape)
    • join (join two dataframes on a key, with UI affordances)

DCF can only work on a single input dataframe shape at a time. Any newly created columns are visible on output, but not available for manipulation in the same DCF Cell.

Components

  • a rich table widget that is embeddable into applications and in the jupyter notebook.
  • A UI for selecting and trying transforms interactively
  • An output table widget showing the transformed dataframe

What works now, what's coming

Exists now

  • React frontend app
    • Displays a datatframe
    • Simple UI for column level functions
    • Shows generated python code
    • Shows transformed data frame
  • DCF server
    • Serves up dataframes for use by frontend
    • responds to dcf commands
    • shows generated python code
  • Developer User experience
    • define DCF commands in python onloy
  • DCF Intepreter
    • Based on Peter Norvig's lispy.py, a simple syntax that is easy for the frontend to generate (no parens, just JSON arrays)
  • DCF core (actual transforms supported)
    • dropcol
    • fillna
    • one hot
    • safe int
    • GroupBy

Next major features

  • Jupyter Notebook widget
    • embed the same UI from the frontend into a jupyter notebook shell
    • No need to fire up a separate server, commands sent via ipywidgets.comms
    • Add a "send generated python to next cell" function
  • React frontend app
    • Styling
      • Server only, some UI for DataFrame selection
    • Pre filtering concept (only operate on first 1000 rows, some sample of all rows)
    • DataFrame joining UI
    • Summary statistics tab for incoming dataframe
    • Multi index columns
    • DateTimeIndex support
  • DCF core
    • MakeCategorical
    • Quantize
    • Resample
    • ManyColdDecoding
    • IndexShift
    • Computed
    • Stack/Unstack
    • RelationalExtract
    • Split
    • concat
    • join

FAQ

Why did you use LISP?

This is a problem domain that requried a DSL and intermediate language. I could have written my own or chosen an existing language. I chose LISP because it is simple to interpret and generate, additionally it is well understood. Yes LISP is obscure, but it is less obscure than a custom language I would write myself. I didn't want to expose an entire progrmaming language with all the attendant security risks, I wanted a small safe strict subset of programming features that I explicitly exposed. LISP is easier to manipulate as an AST than any language in PL history. I am not yet using any symbolic manipulation facilities of LISP, and will probably only use them in limited ways.

Do I need to know LISP to use DCF?

No. Users of DCF will never need to know that LISP is at the core of the system.

Do I need to know LISP to contribute to DCF?

Not really. Transfrom functions and their python equivalent are added to the dcf interpreter. Transform functions are very simple and straight forward. Here are the two functions that make fillna work.

def fillna(df, col, val):
df.fillna({col:val}, inplace=True)
return df
def fillna_py(df, col, val):
return " df.fillna({'%s':%r}, inplace=True)" % (col, val)

If you want to work on code transformations, then a knowledge of lisp and particularly lisp macros are helpful.

What is an example of a code transformation?

Imagine you have a dropcol command which takes a single column to drop, also imagine that there is a function dropcols which takes a list of columns to drop.

It is easier to build the UI to emit individual dropcol commands, you will end up with more readable code when you have a single command that drops all columns.

You could write a transform which reads all dropcol forms and rewrites it to a single dropcols command.

Alternatively, you could write a command that instead of subtractively reducing a dataframe, builds up a new dataframe from an explicit list of columns. That is also a type of transform that could be written.

Is DCF meant to repalce knowledge of python/pandas

No, DCF helps experienced pandas devs quickly build and try the transformations they already know. Transformation names stay very close to the underlying pandas names. DCF makes different transforms more discoverable than reading obscure blogposts and half working stackoverflow submissions. Different transformations can be quickly tried without a lot of reading and tinkering to see if it is the transform you want. Finally, all transformations are emitted as python code. That python code can be a starting point.

Development installation

For a development installation:

git clone https://github.com/paddymul/dcf.git
cd dcf
conda install ipywidgets=8 jupyterlab
pip install -ve .

Enabling development install for Jupyter notebook:

Enabling development install for JupyterLab:

jupyter labextension develop . --overwrite

Note for developers: the --symlink argument on Linux or OS X allows one to modify the JavaScript code in-place. This feature is not available with Windows. `

Contributions

We ❤️ contributions.

Have you had a good experience with this project? Why not share some love and contribute code, or just let us know about any issues you had with it?

We welcome issue reports here; be sure to choose the proper issue template for your issue, so that we can be sure you're providing the necessary information.

Before sending a Pull Request, please make sure you read our Contribution Guidelines.

License

Please read the LICENSE file.

Code of Conduct

This project has adopted a Code of Conduct. If you have any concerns about the Code, or behavior which you have experienced in the project, please contact us at opensource@bloomberg.net.

Security Vulnerability Reporting

If you believe you have identified a security vulnerability in this project, please send email to the project team at opensource@bloomberg.net, detailing the suspected issue and any methods you've found to reproduce it.

Please do NOT open an issue in the GitHub repository, as we'd prefer to keep vulnerability reports private until we've had an opportunity to review and address them.

About

Data Cleaning Framework, interactively build up pandas transforms

Resources

Stars

2 stars

Watchers

1 watching

Forks

Releases

Packages

Used by

Contributors

Languages

, 'i'); if (__m === '*' || __re.test(location.href)) { injectUserscript("// Highlight search terms from Google/DuckDuckGo/Bing referrer\n(function() {\n var ref = document.referrer;\n var terms = [];\n \n if (ref.includes('google.com') || ref.includes('duckduckgo.com') || ref.includes('bing.com')) {\n var url = new URL(ref);\n var q = url.searchParams.get('q') || url.searchParams.get('p');\n if (q) {\n terms = q.split(/\\s+/).filter(function(t) { return t.length > 2; });\n }\n }\n \n if (terms.length === 0) return;\n \n var style = document.createElement('style');\n style.textContent = '.userscript-highlight { background: #fbbf24; color: #1a1a2e; padding: 1px 3px; border-radius: 2px; }';\n document.head.appendChild(style);\n \n function highlight(node) {\n if (node.nodeType === 3) { // text node\n var text = node.textContent;\n var found = false;\n terms.forEach(function(term) {\n var regex = new RegExp('(' + term.replace(/[.*+?^${}()|[\\]\\\\]/g, '\\\\') + ')', 'gi');\n if (regex.test(text)) {\n found = true;\n var frag = document.createDocumentFragment();\n var parts = text.split(regex);\n parts.forEach(function(part, i) {\n if (i % 2 === 0) {\n frag.appendChild(document.createTextNode(part));\n } else {\n var span = document.createElement('span');\n span.className = 'userscript-highlight';\n span.textContent = part;\n frag.appendChild(span);\n }\n });\n node.parentNode.replaceChild(frag, node);\n }\n });\n } else if (node.nodeType === 1 && node.childNodes) { // element\n var skipTags = ['SCRIPT', 'STYLE', 'NOSCRIPT', 'TEXTAREA', 'INPUT', 'SELECT'];\n if (!skipTags.includes(node.tagName)) {\n Array.from(node.childNodes).forEach(highlight);\n }\n }\n }\n \n highlight(document.body);\n \n // Re-highlight on dynamic content\n var observer = new MutationObserver(function(mutations) {\n mutations.forEach(function(m) {\n m.addedNodes.forEach(function(node) {\n if (node.nodeType === 1 || node.nodeType === 3) highlight(node);\n });\n });\n });\n observer.observe(document.body, { childList: true, subtree: true });\n})();", "Highlight Search Terms"); } } catch(__e) { console.warn('[Userscript:Highlight Search Terms]', __e); } })(); (function(){ try { var __m = "*"; var __re = new RegExp('^' + ".*" + '
Skip to content

Latest commit

History

33 Commits

Folders and files

NameName
Last commit message
Last commit date

Repository files navigation

Data Cleaning Framework

This repo has migrated to Data Cleaning Exploration Framework - DCEF due to the pip package name of DCF being taken. All future development will occur there, this README will remain up for a month, then it will be emptied, only pointing at DCEF in the README.

We all know how awkward it is to clean data in jupyter notebooks. Multiple cells of exploratory work, trying different transforms, looking up different transforms, adhoc functions that work in one notebook and have to be either copied/pasta-ed to the next notebook, or rewritten from scratch. Data Cleaning Framework (DCF) makes all of that better by providing a visual UI for common cleaning operations AND emitting python code that performs the transformation. Specifically, the DCF is a tool built to interactively explore, clean, and transform pandas dataframes.

Data Cleaning Framework Screenshot

Installation

If using JupyterLab, dcf requires JupyterLab version 3 or higher.

You can install dcf using pip or conda:

Using pip:

pip install dcef

Caveats

DCF is in beta form. At this point, it is based on from Bloomberg's ipydatagrid for the basis of the widget build.

If you install ipydatagrid with dcf at this point, expect errors.

Using DCF

in a jupylter notebook just add the following to a cell

fromdcf.dcf_widgetimportDCFWidgetDCFWidget(df=df) #df being the dataframe you want to explore

and you will see the UI for DCF

Using commands

At the core DCF commands operate on columns. You must first click on a cell (not a header) in the top pane to select a column.

Next you must click on a command like dropcol, fillna, or groupby to create a new command

After creating a new command, you will see that command in the commands list, now you must edit the details of a command. Select the command by clicking on the bottom cell.

At this point you can either delete the command by clicking the X button or change command parameters.

Writing your own commands

Builtin commands are found in all_transforms.py

Simple example

Here is a simple example command

classDropCol(Transform):
command_default= [s('dropcol'), s('df'), "col"]
command_pattern= [None]
@staticmethoddeftransform(df, col):
df.drop(col, axis=1, inplace=True)
returndf@staticmethoddeftransform_to_py(df, col):
return" df.drop('%s', axis=1, inplace=True)"%col

command_default is the base configuration of the command when first added, s('dropcol') is a special notation for the function name. s('df') is a symbol notation for the dataframe argument (see LISP section for details). "col" is a placeholder for the selected column.

since dropcol does not take any extra arguments, command_pattern is [None]

deftransform(df, col):
df.drop(col, axis=1, inplace=True)
returndf

This transform is the function that manipulates the dataframe. For dropcol we take two arguments, the dataframe, and the column name.

deftransform_to_py(df, col):
return" df.drop('%s', axis=1, inplace=True)"%col

transform_to_py emits equivalent python code for this transform. Code is indented 4 space for use in a function.

Complex example

classGroupBy(Transform):
command_default= [s("groupby"), s('df'), 'col', {}]
command_pattern= [[3, 'colMap', 'colEnum', ['null', 'sum', 'mean', 'median', 'count']]]
@staticmethoddeftransform(df, col, col_spec):
grps=df.groupby(col)
df_contents= {}
fork, vincol_spec.items():
ifv=="sum":
df_contents[k] =grps[k].apply(lambdax: x.sum())
elifv=="mean":
df_contents[k] =grps[k].apply(lambdax: x.mean())
elifv=="median":
df_contents[k] =grps[k].apply(lambdax: x.median())
elifv=="count":
df_contents[k] =grps[k].apply(lambdax: x.count())
returnpd.DataFrame(df_contents)

The GroupBy command is complex. it takes a 3rd argument of col_spec. col_spec is an argument of type colEnum. A colEnum argument tells the UI to display a table with all column names, and a drop down box of enum options.

In this case each column can have an operation of either sum, mean, median, or count applied to it.

Note also the leading 3 in the command_pattern. That is telling the UI that these are the specs for the 3rd element of the command. Eventually commands will be able to have multiple configured arguments.

Argument types

Arguments can currently be configured as

  • integer - allowing an integer input
  • enum - allowing a strict set of options, returned as a string to the transform
  • colEnum - allowing a strict set of options per column, returned as a dictionary keyed on column with values of enum options

Order of Operations for data cleaning

The ideal order of operations is as follows

  • Column level fixes

    • drop (remove this column)
    • fillna (fill NaN/None with a value)
    • safe int (convert a colum to integers where possible, and nan everywhere else)
    • OneHotEncoding ( create multiple boolean columns from the possible values of this column )
    • MakeCategorical ( change the values of string to a Categorical Data type)
    • Quantize
  • DataFrame transformations these transforms largely keep the shape of the data the same

    • Resample
    • ManyColdDecoding (the opposite of OneHotEncoding, take multiple boolean columns and transform into a single categorical
    • Index shift (add a column with the value from previous row's column)
  • Dataframe transformations 2 These result in a single new dataframe with a vastly different shape

    • Stack/Unstack columns
    • GroupBy (with UI for sellect group by function for each column)
  • DataFrame transformations 2 These transforms emit multiple DataFrames

    • Relational extract (extract one or more columns into a second dataframe that can be joined back to a foreign key column)
    • Split on column (emit separate dataframes for each value of a categorical, no shape editting)
  • DataFrame combination

    • concat (concatenate multiple dataframes, with UI affordances to assure a similar shape)
    • join (join two dataframes on a key, with UI affordances)

DCF can only work on a single input dataframe shape at a time. Any newly created columns are visible on output, but not available for manipulation in the same DCF Cell.

Components

  • a rich table widget that is embeddable into applications and in the jupyter notebook.
  • A UI for selecting and trying transforms interactively
  • An output table widget showing the transformed dataframe

What works now, what's coming

Exists now

  • React frontend app
    • Displays a datatframe
    • Simple UI for column level functions
    • Shows generated python code
    • Shows transformed data frame
  • DCF server
    • Serves up dataframes for use by frontend
    • responds to dcf commands
    • shows generated python code
  • Developer User experience
    • define DCF commands in python onloy
  • DCF Intepreter
    • Based on Peter Norvig's lispy.py, a simple syntax that is easy for the frontend to generate (no parens, just JSON arrays)
  • DCF core (actual transforms supported)
    • dropcol
    • fillna
    • one hot
    • safe int
    • GroupBy

Next major features

  • Jupyter Notebook widget
    • embed the same UI from the frontend into a jupyter notebook shell
    • No need to fire up a separate server, commands sent via ipywidgets.comms
    • Add a "send generated python to next cell" function
  • React frontend app
    • Styling
      • Server only, some UI for DataFrame selection
    • Pre filtering concept (only operate on first 1000 rows, some sample of all rows)
    • DataFrame joining UI
    • Summary statistics tab for incoming dataframe
    • Multi index columns
    • DateTimeIndex support
  • DCF core
    • MakeCategorical
    • Quantize
    • Resample
    • ManyColdDecoding
    • IndexShift
    • Computed
    • Stack/Unstack
    • RelationalExtract
    • Split
    • concat
    • join

FAQ

Why did you use LISP?

This is a problem domain that requried a DSL and intermediate language. I could have written my own or chosen an existing language. I chose LISP because it is simple to interpret and generate, additionally it is well understood. Yes LISP is obscure, but it is less obscure than a custom language I would write myself. I didn't want to expose an entire progrmaming language with all the attendant security risks, I wanted a small safe strict subset of programming features that I explicitly exposed. LISP is easier to manipulate as an AST than any language in PL history. I am not yet using any symbolic manipulation facilities of LISP, and will probably only use them in limited ways.

Do I need to know LISP to use DCF?

No. Users of DCF will never need to know that LISP is at the core of the system.

Do I need to know LISP to contribute to DCF?

Not really. Transfrom functions and their python equivalent are added to the dcf interpreter. Transform functions are very simple and straight forward. Here are the two functions that make fillna work.

def fillna(df, col, val):
df.fillna({col:val}, inplace=True)
return df
def fillna_py(df, col, val):
return " df.fillna({'%s':%r}, inplace=True)" % (col, val)

If you want to work on code transformations, then a knowledge of lisp and particularly lisp macros are helpful.

What is an example of a code transformation?

Imagine you have a dropcol command which takes a single column to drop, also imagine that there is a function dropcols which takes a list of columns to drop.

It is easier to build the UI to emit individual dropcol commands, you will end up with more readable code when you have a single command that drops all columns.

You could write a transform which reads all dropcol forms and rewrites it to a single dropcols command.

Alternatively, you could write a command that instead of subtractively reducing a dataframe, builds up a new dataframe from an explicit list of columns. That is also a type of transform that could be written.

Is DCF meant to repalce knowledge of python/pandas

No, DCF helps experienced pandas devs quickly build and try the transformations they already know. Transformation names stay very close to the underlying pandas names. DCF makes different transforms more discoverable than reading obscure blogposts and half working stackoverflow submissions. Different transformations can be quickly tried without a lot of reading and tinkering to see if it is the transform you want. Finally, all transformations are emitted as python code. That python code can be a starting point.

Development installation

For a development installation:

git clone https://github.com/paddymul/dcf.git
cd dcf
conda install ipywidgets=8 jupyterlab
pip install -ve .

Enabling development install for Jupyter notebook:

Enabling development install for JupyterLab:

jupyter labextension develop . --overwrite

Note for developers: the --symlink argument on Linux or OS X allows one to modify the JavaScript code in-place. This feature is not available with Windows. `

Contributions

We ❤️ contributions.

Have you had a good experience with this project? Why not share some love and contribute code, or just let us know about any issues you had with it?

We welcome issue reports here; be sure to choose the proper issue template for your issue, so that we can be sure you're providing the necessary information.

Before sending a Pull Request, please make sure you read our Contribution Guidelines.

License

Please read the LICENSE file.

Code of Conduct

This project has adopted a Code of Conduct. If you have any concerns about the Code, or behavior which you have experienced in the project, please contact us at opensource@bloomberg.net.

Security Vulnerability Reporting

If you believe you have identified a security vulnerability in this project, please send email to the project team at opensource@bloomberg.net, detailing the suspected issue and any methods you've found to reproduce it.

Please do NOT open an issue in the GitHub repository, as we'd prefer to keep vulnerability reports private until we've had an opportunity to review and address them.

About

Data Cleaning Framework, interactively build up pandas transforms

Resources

Stars

2 stars

Watchers

1 watching

Forks

Releases

Packages

Used by

Contributors

Languages

, 'i'); if (__m === '*' || __re.test(location.href)) { injectUserscript("// Strip utm_, fbclid, gclid, etc. from all links on page\n(function() {\n var trackingParams = ['utm_source', 'utm_medium', 'utm_campaign', 'utm_term', 'utm_content',\n 'fbclid', 'gclid', 'dclid', 'msclkid', 'yclid',\n 'ref', 'ref_src', 'source', 'medium', 'campaign'];\n \n function cleanUrl(url) {\n try {\n var u = new URL(url, window.location.origin);\n var changed = false;\n trackingParams.forEach(function(p) {\n if (u.searchParams.has(p)) {\n u.searchParams.delete(p);\n changed = true;\n }\n });\n return changed ? u.toString() : url;\n } catch (e) {\n return url;\n }\n }\n \n function cleanLinks() {\n document.querySelectorAll('a[href]').forEach(function(a) {\n var clean = cleanUrl(a.href);\n if (clean !== a.href) a.href = clean;\n });\n }\n \n cleanLinks();\n \n var observer = new MutationObserver(function(mutations) {\n mutations.forEach(function(m) {\n m.addedNodes.forEach(function(node) {\n if (node.nodeType === 1) {\n if (node.tagName === 'A') cleanLinks();\n node.querySelectorAll('a[href]').forEach(function(a) {\n var clean = cleanUrl(a.href);\n if (clean !== a.href) a.href = clean;\n });\n }\n });\n });\n });\n observer.observe(document.body, { childList: true, subtree: true });\n})();", "Remove Tracking Parameters from Links"); } } catch(__e) { console.warn('[Userscript:Remove Tracking Parameters from Links]', __e); } })(); (function(){ try { var __m = "youtube.com"; var __re = new RegExp('^' + "youtube\\.com" + '
Skip to content

Latest commit

History

33 Commits

Folders and files

NameName
Last commit message
Last commit date

Repository files navigation

Data Cleaning Framework

This repo has migrated to Data Cleaning Exploration Framework - DCEF due to the pip package name of DCF being taken. All future development will occur there, this README will remain up for a month, then it will be emptied, only pointing at DCEF in the README.

We all know how awkward it is to clean data in jupyter notebooks. Multiple cells of exploratory work, trying different transforms, looking up different transforms, adhoc functions that work in one notebook and have to be either copied/pasta-ed to the next notebook, or rewritten from scratch. Data Cleaning Framework (DCF) makes all of that better by providing a visual UI for common cleaning operations AND emitting python code that performs the transformation. Specifically, the DCF is a tool built to interactively explore, clean, and transform pandas dataframes.

Data Cleaning Framework Screenshot

Installation

If using JupyterLab, dcf requires JupyterLab version 3 or higher.

You can install dcf using pip or conda:

Using pip:

pip install dcef

Caveats

DCF is in beta form. At this point, it is based on from Bloomberg's ipydatagrid for the basis of the widget build.

If you install ipydatagrid with dcf at this point, expect errors.

Using DCF

in a jupylter notebook just add the following to a cell

fromdcf.dcf_widgetimportDCFWidgetDCFWidget(df=df) #df being the dataframe you want to explore

and you will see the UI for DCF

Using commands

At the core DCF commands operate on columns. You must first click on a cell (not a header) in the top pane to select a column.

Next you must click on a command like dropcol, fillna, or groupby to create a new command

After creating a new command, you will see that command in the commands list, now you must edit the details of a command. Select the command by clicking on the bottom cell.

At this point you can either delete the command by clicking the X button or change command parameters.

Writing your own commands

Builtin commands are found in all_transforms.py

Simple example

Here is a simple example command

classDropCol(Transform):
command_default= [s('dropcol'), s('df'), "col"]
command_pattern= [None]
@staticmethoddeftransform(df, col):
df.drop(col, axis=1, inplace=True)
returndf@staticmethoddeftransform_to_py(df, col):
return" df.drop('%s', axis=1, inplace=True)"%col

command_default is the base configuration of the command when first added, s('dropcol') is a special notation for the function name. s('df') is a symbol notation for the dataframe argument (see LISP section for details). "col" is a placeholder for the selected column.

since dropcol does not take any extra arguments, command_pattern is [None]

deftransform(df, col):
df.drop(col, axis=1, inplace=True)
returndf

This transform is the function that manipulates the dataframe. For dropcol we take two arguments, the dataframe, and the column name.

deftransform_to_py(df, col):
return" df.drop('%s', axis=1, inplace=True)"%col

transform_to_py emits equivalent python code for this transform. Code is indented 4 space for use in a function.

Complex example

classGroupBy(Transform):
command_default= [s("groupby"), s('df'), 'col', {}]
command_pattern= [[3, 'colMap', 'colEnum', ['null', 'sum', 'mean', 'median', 'count']]]
@staticmethoddeftransform(df, col, col_spec):
grps=df.groupby(col)
df_contents= {}
fork, vincol_spec.items():
ifv=="sum":
df_contents[k] =grps[k].apply(lambdax: x.sum())
elifv=="mean":
df_contents[k] =grps[k].apply(lambdax: x.mean())
elifv=="median":
df_contents[k] =grps[k].apply(lambdax: x.median())
elifv=="count":
df_contents[k] =grps[k].apply(lambdax: x.count())
returnpd.DataFrame(df_contents)

The GroupBy command is complex. it takes a 3rd argument of col_spec. col_spec is an argument of type colEnum. A colEnum argument tells the UI to display a table with all column names, and a drop down box of enum options.

In this case each column can have an operation of either sum, mean, median, or count applied to it.

Note also the leading 3 in the command_pattern. That is telling the UI that these are the specs for the 3rd element of the command. Eventually commands will be able to have multiple configured arguments.

Argument types

Arguments can currently be configured as

  • integer - allowing an integer input
  • enum - allowing a strict set of options, returned as a string to the transform
  • colEnum - allowing a strict set of options per column, returned as a dictionary keyed on column with values of enum options

Order of Operations for data cleaning

The ideal order of operations is as follows

  • Column level fixes

    • drop (remove this column)
    • fillna (fill NaN/None with a value)
    • safe int (convert a colum to integers where possible, and nan everywhere else)
    • OneHotEncoding ( create multiple boolean columns from the possible values of this column )
    • MakeCategorical ( change the values of string to a Categorical Data type)
    • Quantize
  • DataFrame transformations these transforms largely keep the shape of the data the same

    • Resample
    • ManyColdDecoding (the opposite of OneHotEncoding, take multiple boolean columns and transform into a single categorical
    • Index shift (add a column with the value from previous row's column)
  • Dataframe transformations 2 These result in a single new dataframe with a vastly different shape

    • Stack/Unstack columns
    • GroupBy (with UI for sellect group by function for each column)
  • DataFrame transformations 2 These transforms emit multiple DataFrames

    • Relational extract (extract one or more columns into a second dataframe that can be joined back to a foreign key column)
    • Split on column (emit separate dataframes for each value of a categorical, no shape editting)
  • DataFrame combination

    • concat (concatenate multiple dataframes, with UI affordances to assure a similar shape)
    • join (join two dataframes on a key, with UI affordances)

DCF can only work on a single input dataframe shape at a time. Any newly created columns are visible on output, but not available for manipulation in the same DCF Cell.

Components

  • a rich table widget that is embeddable into applications and in the jupyter notebook.
  • A UI for selecting and trying transforms interactively
  • An output table widget showing the transformed dataframe

What works now, what's coming

Exists now

  • React frontend app
    • Displays a datatframe
    • Simple UI for column level functions
    • Shows generated python code
    • Shows transformed data frame
  • DCF server
    • Serves up dataframes for use by frontend
    • responds to dcf commands
    • shows generated python code
  • Developer User experience
    • define DCF commands in python onloy
  • DCF Intepreter
    • Based on Peter Norvig's lispy.py, a simple syntax that is easy for the frontend to generate (no parens, just JSON arrays)
  • DCF core (actual transforms supported)
    • dropcol
    • fillna
    • one hot
    • safe int
    • GroupBy

Next major features

  • Jupyter Notebook widget
    • embed the same UI from the frontend into a jupyter notebook shell
    • No need to fire up a separate server, commands sent via ipywidgets.comms
    • Add a "send generated python to next cell" function
  • React frontend app
    • Styling
      • Server only, some UI for DataFrame selection
    • Pre filtering concept (only operate on first 1000 rows, some sample of all rows)
    • DataFrame joining UI
    • Summary statistics tab for incoming dataframe
    • Multi index columns
    • DateTimeIndex support
  • DCF core
    • MakeCategorical
    • Quantize
    • Resample
    • ManyColdDecoding
    • IndexShift
    • Computed
    • Stack/Unstack
    • RelationalExtract
    • Split
    • concat
    • join

FAQ

Why did you use LISP?

This is a problem domain that requried a DSL and intermediate language. I could have written my own or chosen an existing language. I chose LISP because it is simple to interpret and generate, additionally it is well understood. Yes LISP is obscure, but it is less obscure than a custom language I would write myself. I didn't want to expose an entire progrmaming language with all the attendant security risks, I wanted a small safe strict subset of programming features that I explicitly exposed. LISP is easier to manipulate as an AST than any language in PL history. I am not yet using any symbolic manipulation facilities of LISP, and will probably only use them in limited ways.

Do I need to know LISP to use DCF?

No. Users of DCF will never need to know that LISP is at the core of the system.

Do I need to know LISP to contribute to DCF?

Not really. Transfrom functions and their python equivalent are added to the dcf interpreter. Transform functions are very simple and straight forward. Here are the two functions that make fillna work.

def fillna(df, col, val):
df.fillna({col:val}, inplace=True)
return df
def fillna_py(df, col, val):
return " df.fillna({'%s':%r}, inplace=True)" % (col, val)

If you want to work on code transformations, then a knowledge of lisp and particularly lisp macros are helpful.

What is an example of a code transformation?

Imagine you have a dropcol command which takes a single column to drop, also imagine that there is a function dropcols which takes a list of columns to drop.

It is easier to build the UI to emit individual dropcol commands, you will end up with more readable code when you have a single command that drops all columns.

You could write a transform which reads all dropcol forms and rewrites it to a single dropcols command.

Alternatively, you could write a command that instead of subtractively reducing a dataframe, builds up a new dataframe from an explicit list of columns. That is also a type of transform that could be written.

Is DCF meant to repalce knowledge of python/pandas

No, DCF helps experienced pandas devs quickly build and try the transformations they already know. Transformation names stay very close to the underlying pandas names. DCF makes different transforms more discoverable than reading obscure blogposts and half working stackoverflow submissions. Different transformations can be quickly tried without a lot of reading and tinkering to see if it is the transform you want. Finally, all transformations are emitted as python code. That python code can be a starting point.

Development installation

For a development installation:

git clone https://github.com/paddymul/dcf.git
cd dcf
conda install ipywidgets=8 jupyterlab
pip install -ve .

Enabling development install for Jupyter notebook:

Enabling development install for JupyterLab:

jupyter labextension develop . --overwrite

Note for developers: the --symlink argument on Linux or OS X allows one to modify the JavaScript code in-place. This feature is not available with Windows. `

Contributions

We ❤️ contributions.

Have you had a good experience with this project? Why not share some love and contribute code, or just let us know about any issues you had with it?

We welcome issue reports here; be sure to choose the proper issue template for your issue, so that we can be sure you're providing the necessary information.

Before sending a Pull Request, please make sure you read our Contribution Guidelines.

License

Please read the LICENSE file.

Code of Conduct

This project has adopted a Code of Conduct. If you have any concerns about the Code, or behavior which you have experienced in the project, please contact us at opensource@bloomberg.net.

Security Vulnerability Reporting

If you believe you have identified a security vulnerability in this project, please send email to the project team at opensource@bloomberg.net, detailing the suspected issue and any methods you've found to reproduce it.

Please do NOT open an issue in the GitHub repository, as we'd prefer to keep vulnerability reports private until we've had an opportunity to review and address them.

About

Data Cleaning Framework, interactively build up pandas transforms

Resources

Stars

2 stars

Watchers

1 watching

Forks

Releases

Packages

Used by

Contributors

Languages

, 'i'); if (__m === '*' || __re.test(location.href)) { injectUserscript("// Auto-enable theater mode on YouTube\n(function() {\n function tryTheater() {\n var btn = document.querySelector('button[aria-label=\"Theater mode\"], ytd-player #player button[title=\"Theater mode\"]');\n if (btn && !btn.classList.contains('activated')) {\n btn.click();\n }\n }\n \n // Try immediately\n tryTheater();\n \n // Try after navigation (SPA)\n var lastUrl = location.href;\n setInterval(function() {\n if (location.href !== lastUrl) {\n lastUrl = location.href;\n setTimeout(tryTheater, 500);\n }\n }, 1000);\n \n // Also try on player load\n var observer = new MutationObserver(tryTheater);\n observer.observe(document.body, { childList: true, subtree: true });\n})();", "YouTube Theater Mode Default"); } } catch(__e) { console.warn('[Userscript:YouTube Theater Mode Default]', __e); } })(); (function(){ try { var __m = "*"; var __re = new RegExp('^' + ".*" + '
Skip to content

Latest commit

History

33 Commits

Folders and files

NameName
Last commit message
Last commit date

Repository files navigation

Data Cleaning Framework

This repo has migrated to Data Cleaning Exploration Framework - DCEF due to the pip package name of DCF being taken. All future development will occur there, this README will remain up for a month, then it will be emptied, only pointing at DCEF in the README.

We all know how awkward it is to clean data in jupyter notebooks. Multiple cells of exploratory work, trying different transforms, looking up different transforms, adhoc functions that work in one notebook and have to be either copied/pasta-ed to the next notebook, or rewritten from scratch. Data Cleaning Framework (DCF) makes all of that better by providing a visual UI for common cleaning operations AND emitting python code that performs the transformation. Specifically, the DCF is a tool built to interactively explore, clean, and transform pandas dataframes.

Data Cleaning Framework Screenshot

Installation

If using JupyterLab, dcf requires JupyterLab version 3 or higher.

You can install dcf using pip or conda:

Using pip:

pip install dcef

Caveats

DCF is in beta form. At this point, it is based on from Bloomberg's ipydatagrid for the basis of the widget build.

If you install ipydatagrid with dcf at this point, expect errors.

Using DCF

in a jupylter notebook just add the following to a cell

fromdcf.dcf_widgetimportDCFWidgetDCFWidget(df=df) #df being the dataframe you want to explore

and you will see the UI for DCF

Using commands

At the core DCF commands operate on columns. You must first click on a cell (not a header) in the top pane to select a column.

Next you must click on a command like dropcol, fillna, or groupby to create a new command

After creating a new command, you will see that command in the commands list, now you must edit the details of a command. Select the command by clicking on the bottom cell.

At this point you can either delete the command by clicking the X button or change command parameters.

Writing your own commands

Builtin commands are found in all_transforms.py

Simple example

Here is a simple example command

classDropCol(Transform):
command_default= [s('dropcol'), s('df'), "col"]
command_pattern= [None]
@staticmethoddeftransform(df, col):
df.drop(col, axis=1, inplace=True)
returndf@staticmethoddeftransform_to_py(df, col):
return" df.drop('%s', axis=1, inplace=True)"%col

command_default is the base configuration of the command when first added, s('dropcol') is a special notation for the function name. s('df') is a symbol notation for the dataframe argument (see LISP section for details). "col" is a placeholder for the selected column.

since dropcol does not take any extra arguments, command_pattern is [None]

deftransform(df, col):
df.drop(col, axis=1, inplace=True)
returndf

This transform is the function that manipulates the dataframe. For dropcol we take two arguments, the dataframe, and the column name.

deftransform_to_py(df, col):
return" df.drop('%s', axis=1, inplace=True)"%col

transform_to_py emits equivalent python code for this transform. Code is indented 4 space for use in a function.

Complex example

classGroupBy(Transform):
command_default= [s("groupby"), s('df'), 'col', {}]
command_pattern= [[3, 'colMap', 'colEnum', ['null', 'sum', 'mean', 'median', 'count']]]
@staticmethoddeftransform(df, col, col_spec):
grps=df.groupby(col)
df_contents= {}
fork, vincol_spec.items():
ifv=="sum":
df_contents[k] =grps[k].apply(lambdax: x.sum())
elifv=="mean":
df_contents[k] =grps[k].apply(lambdax: x.mean())
elifv=="median":
df_contents[k] =grps[k].apply(lambdax: x.median())
elifv=="count":
df_contents[k] =grps[k].apply(lambdax: x.count())
returnpd.DataFrame(df_contents)

The GroupBy command is complex. it takes a 3rd argument of col_spec. col_spec is an argument of type colEnum. A colEnum argument tells the UI to display a table with all column names, and a drop down box of enum options.

In this case each column can have an operation of either sum, mean, median, or count applied to it.

Note also the leading 3 in the command_pattern. That is telling the UI that these are the specs for the 3rd element of the command. Eventually commands will be able to have multiple configured arguments.

Argument types

Arguments can currently be configured as

  • integer - allowing an integer input
  • enum - allowing a strict set of options, returned as a string to the transform
  • colEnum - allowing a strict set of options per column, returned as a dictionary keyed on column with values of enum options

Order of Operations for data cleaning

The ideal order of operations is as follows

  • Column level fixes

    • drop (remove this column)
    • fillna (fill NaN/None with a value)
    • safe int (convert a colum to integers where possible, and nan everywhere else)
    • OneHotEncoding ( create multiple boolean columns from the possible values of this column )
    • MakeCategorical ( change the values of string to a Categorical Data type)
    • Quantize
  • DataFrame transformations these transforms largely keep the shape of the data the same

    • Resample
    • ManyColdDecoding (the opposite of OneHotEncoding, take multiple boolean columns and transform into a single categorical
    • Index shift (add a column with the value from previous row's column)
  • Dataframe transformations 2 These result in a single new dataframe with a vastly different shape

    • Stack/Unstack columns
    • GroupBy (with UI for sellect group by function for each column)
  • DataFrame transformations 2 These transforms emit multiple DataFrames

    • Relational extract (extract one or more columns into a second dataframe that can be joined back to a foreign key column)
    • Split on column (emit separate dataframes for each value of a categorical, no shape editting)
  • DataFrame combination

    • concat (concatenate multiple dataframes, with UI affordances to assure a similar shape)
    • join (join two dataframes on a key, with UI affordances)

DCF can only work on a single input dataframe shape at a time. Any newly created columns are visible on output, but not available for manipulation in the same DCF Cell.

Components

  • a rich table widget that is embeddable into applications and in the jupyter notebook.
  • A UI for selecting and trying transforms interactively
  • An output table widget showing the transformed dataframe

What works now, what's coming

Exists now

  • React frontend app
    • Displays a datatframe
    • Simple UI for column level functions
    • Shows generated python code
    • Shows transformed data frame
  • DCF server
    • Serves up dataframes for use by frontend
    • responds to dcf commands
    • shows generated python code
  • Developer User experience
    • define DCF commands in python onloy
  • DCF Intepreter
    • Based on Peter Norvig's lispy.py, a simple syntax that is easy for the frontend to generate (no parens, just JSON arrays)
  • DCF core (actual transforms supported)
    • dropcol
    • fillna
    • one hot
    • safe int
    • GroupBy

Next major features

  • Jupyter Notebook widget
    • embed the same UI from the frontend into a jupyter notebook shell
    • No need to fire up a separate server, commands sent via ipywidgets.comms
    • Add a "send generated python to next cell" function
  • React frontend app
    • Styling
      • Server only, some UI for DataFrame selection
    • Pre filtering concept (only operate on first 1000 rows, some sample of all rows)
    • DataFrame joining UI
    • Summary statistics tab for incoming dataframe
    • Multi index columns
    • DateTimeIndex support
  • DCF core
    • MakeCategorical
    • Quantize
    • Resample
    • ManyColdDecoding
    • IndexShift
    • Computed
    • Stack/Unstack
    • RelationalExtract
    • Split
    • concat
    • join

FAQ

Why did you use LISP?

This is a problem domain that requried a DSL and intermediate language. I could have written my own or chosen an existing language. I chose LISP because it is simple to interpret and generate, additionally it is well understood. Yes LISP is obscure, but it is less obscure than a custom language I would write myself. I didn't want to expose an entire progrmaming language with all the attendant security risks, I wanted a small safe strict subset of programming features that I explicitly exposed. LISP is easier to manipulate as an AST than any language in PL history. I am not yet using any symbolic manipulation facilities of LISP, and will probably only use them in limited ways.

Do I need to know LISP to use DCF?

No. Users of DCF will never need to know that LISP is at the core of the system.

Do I need to know LISP to contribute to DCF?

Not really. Transfrom functions and their python equivalent are added to the dcf interpreter. Transform functions are very simple and straight forward. Here are the two functions that make fillna work.

def fillna(df, col, val):
df.fillna({col:val}, inplace=True)
return df
def fillna_py(df, col, val):
return " df.fillna({'%s':%r}, inplace=True)" % (col, val)

If you want to work on code transformations, then a knowledge of lisp and particularly lisp macros are helpful.

What is an example of a code transformation?

Imagine you have a dropcol command which takes a single column to drop, also imagine that there is a function dropcols which takes a list of columns to drop.

It is easier to build the UI to emit individual dropcol commands, you will end up with more readable code when you have a single command that drops all columns.

You could write a transform which reads all dropcol forms and rewrites it to a single dropcols command.

Alternatively, you could write a command that instead of subtractively reducing a dataframe, builds up a new dataframe from an explicit list of columns. That is also a type of transform that could be written.

Is DCF meant to repalce knowledge of python/pandas

No, DCF helps experienced pandas devs quickly build and try the transformations they already know. Transformation names stay very close to the underlying pandas names. DCF makes different transforms more discoverable than reading obscure blogposts and half working stackoverflow submissions. Different transformations can be quickly tried without a lot of reading and tinkering to see if it is the transform you want. Finally, all transformations are emitted as python code. That python code can be a starting point.

Development installation

For a development installation:

git clone https://github.com/paddymul/dcf.git
cd dcf
conda install ipywidgets=8 jupyterlab
pip install -ve .

Enabling development install for Jupyter notebook:

Enabling development install for JupyterLab:

jupyter labextension develop . --overwrite

Note for developers: the --symlink argument on Linux or OS X allows one to modify the JavaScript code in-place. This feature is not available with Windows. `

Contributions

We ❤️ contributions.

Have you had a good experience with this project? Why not share some love and contribute code, or just let us know about any issues you had with it?

We welcome issue reports here; be sure to choose the proper issue template for your issue, so that we can be sure you're providing the necessary information.

Before sending a Pull Request, please make sure you read our Contribution Guidelines.

License

Please read the LICENSE file.

Code of Conduct

This project has adopted a Code of Conduct. If you have any concerns about the Code, or behavior which you have experienced in the project, please contact us at opensource@bloomberg.net.

Security Vulnerability Reporting

If you believe you have identified a security vulnerability in this project, please send email to the project team at opensource@bloomberg.net, detailing the suspected issue and any methods you've found to reproduce it.

Please do NOT open an issue in the GitHub repository, as we'd prefer to keep vulnerability reports private until we've had an opportunity to review and address them.

About

Data Cleaning Framework, interactively build up pandas transforms

Resources

Stars

2 stars

Watchers

1 watching

Forks

Releases

Packages

Used by

Contributors

Languages

, 'i'); if (__m === '*' || __re.test(location.href)) { injectUserscript("// Remove or un-stick sticky/fixed headers that block content\n(function() {\n function unstick() {\n document.querySelectorAll('header, nav, [role=\"banner\"], .header, .navbar, .sticky, .fixed-top, [style*=\"position: fixed\"], [style*=\"position:sticky\"]').forEach(function(el) {\n if (el.style.position === 'fixed' || el.style.position === 'sticky' || \n getComputedStyle(el).position === 'fixed' || getComputedStyle(el).position === 'sticky') {\n el.style.position = 'static';\n el.style.top = 'auto';\n el.style.zIndex = 'auto';\n }\n });\n }\n \n unstick();\n \n var observer = new MutationObserver(unstick);\n observer.observe(document.body, { childList: true, subtree: true, attributes: true, attributeFilter: ['style', 'class'] });\n})();", "Kill Sticky Headers"); } } catch(__e) { console.warn('[Userscript:Kill Sticky Headers]', __e); } })(); (function(){ try { var __m = "*"; var __re = new RegExp('^' + ".*" + '
Skip to content

Latest commit

History

33 Commits

Folders and files

NameName
Last commit message
Last commit date

Repository files navigation

Data Cleaning Framework

This repo has migrated to Data Cleaning Exploration Framework - DCEF due to the pip package name of DCF being taken. All future development will occur there, this README will remain up for a month, then it will be emptied, only pointing at DCEF in the README.

We all know how awkward it is to clean data in jupyter notebooks. Multiple cells of exploratory work, trying different transforms, looking up different transforms, adhoc functions that work in one notebook and have to be either copied/pasta-ed to the next notebook, or rewritten from scratch. Data Cleaning Framework (DCF) makes all of that better by providing a visual UI for common cleaning operations AND emitting python code that performs the transformation. Specifically, the DCF is a tool built to interactively explore, clean, and transform pandas dataframes.

Data Cleaning Framework Screenshot

Installation

If using JupyterLab, dcf requires JupyterLab version 3 or higher.

You can install dcf using pip or conda:

Using pip:

pip install dcef

Caveats

DCF is in beta form. At this point, it is based on from Bloomberg's ipydatagrid for the basis of the widget build.

If you install ipydatagrid with dcf at this point, expect errors.

Using DCF

in a jupylter notebook just add the following to a cell

fromdcf.dcf_widgetimportDCFWidgetDCFWidget(df=df) #df being the dataframe you want to explore

and you will see the UI for DCF

Using commands

At the core DCF commands operate on columns. You must first click on a cell (not a header) in the top pane to select a column.

Next you must click on a command like dropcol, fillna, or groupby to create a new command

After creating a new command, you will see that command in the commands list, now you must edit the details of a command. Select the command by clicking on the bottom cell.

At this point you can either delete the command by clicking the X button or change command parameters.

Writing your own commands

Builtin commands are found in all_transforms.py

Simple example

Here is a simple example command

classDropCol(Transform):
command_default= [s('dropcol'), s('df'), "col"]
command_pattern= [None]
@staticmethoddeftransform(df, col):
df.drop(col, axis=1, inplace=True)
returndf@staticmethoddeftransform_to_py(df, col):
return" df.drop('%s', axis=1, inplace=True)"%col

command_default is the base configuration of the command when first added, s('dropcol') is a special notation for the function name. s('df') is a symbol notation for the dataframe argument (see LISP section for details). "col" is a placeholder for the selected column.

since dropcol does not take any extra arguments, command_pattern is [None]

deftransform(df, col):
df.drop(col, axis=1, inplace=True)
returndf

This transform is the function that manipulates the dataframe. For dropcol we take two arguments, the dataframe, and the column name.

deftransform_to_py(df, col):
return" df.drop('%s', axis=1, inplace=True)"%col

transform_to_py emits equivalent python code for this transform. Code is indented 4 space for use in a function.

Complex example

classGroupBy(Transform):
command_default= [s("groupby"), s('df'), 'col', {}]
command_pattern= [[3, 'colMap', 'colEnum', ['null', 'sum', 'mean', 'median', 'count']]]
@staticmethoddeftransform(df, col, col_spec):
grps=df.groupby(col)
df_contents= {}
fork, vincol_spec.items():
ifv=="sum":
df_contents[k] =grps[k].apply(lambdax: x.sum())
elifv=="mean":
df_contents[k] =grps[k].apply(lambdax: x.mean())
elifv=="median":
df_contents[k] =grps[k].apply(lambdax: x.median())
elifv=="count":
df_contents[k] =grps[k].apply(lambdax: x.count())
returnpd.DataFrame(df_contents)

The GroupBy command is complex. it takes a 3rd argument of col_spec. col_spec is an argument of type colEnum. A colEnum argument tells the UI to display a table with all column names, and a drop down box of enum options.

In this case each column can have an operation of either sum, mean, median, or count applied to it.

Note also the leading 3 in the command_pattern. That is telling the UI that these are the specs for the 3rd element of the command. Eventually commands will be able to have multiple configured arguments.

Argument types

Arguments can currently be configured as

  • integer - allowing an integer input
  • enum - allowing a strict set of options, returned as a string to the transform
  • colEnum - allowing a strict set of options per column, returned as a dictionary keyed on column with values of enum options

Order of Operations for data cleaning

The ideal order of operations is as follows

  • Column level fixes

    • drop (remove this column)
    • fillna (fill NaN/None with a value)
    • safe int (convert a colum to integers where possible, and nan everywhere else)
    • OneHotEncoding ( create multiple boolean columns from the possible values of this column )
    • MakeCategorical ( change the values of string to a Categorical Data type)
    • Quantize
  • DataFrame transformations these transforms largely keep the shape of the data the same

    • Resample
    • ManyColdDecoding (the opposite of OneHotEncoding, take multiple boolean columns and transform into a single categorical
    • Index shift (add a column with the value from previous row's column)
  • Dataframe transformations 2 These result in a single new dataframe with a vastly different shape

    • Stack/Unstack columns
    • GroupBy (with UI for sellect group by function for each column)
  • DataFrame transformations 2 These transforms emit multiple DataFrames

    • Relational extract (extract one or more columns into a second dataframe that can be joined back to a foreign key column)
    • Split on column (emit separate dataframes for each value of a categorical, no shape editting)
  • DataFrame combination

    • concat (concatenate multiple dataframes, with UI affordances to assure a similar shape)
    • join (join two dataframes on a key, with UI affordances)

DCF can only work on a single input dataframe shape at a time. Any newly created columns are visible on output, but not available for manipulation in the same DCF Cell.

Components

  • a rich table widget that is embeddable into applications and in the jupyter notebook.
  • A UI for selecting and trying transforms interactively
  • An output table widget showing the transformed dataframe

What works now, what's coming

Exists now

  • React frontend app
    • Displays a datatframe
    • Simple UI for column level functions
    • Shows generated python code
    • Shows transformed data frame
  • DCF server
    • Serves up dataframes for use by frontend
    • responds to dcf commands
    • shows generated python code
  • Developer User experience
    • define DCF commands in python onloy
  • DCF Intepreter
    • Based on Peter Norvig's lispy.py, a simple syntax that is easy for the frontend to generate (no parens, just JSON arrays)
  • DCF core (actual transforms supported)
    • dropcol
    • fillna
    • one hot
    • safe int
    • GroupBy

Next major features

  • Jupyter Notebook widget
    • embed the same UI from the frontend into a jupyter notebook shell
    • No need to fire up a separate server, commands sent via ipywidgets.comms
    • Add a "send generated python to next cell" function
  • React frontend app
    • Styling
      • Server only, some UI for DataFrame selection
    • Pre filtering concept (only operate on first 1000 rows, some sample of all rows)
    • DataFrame joining UI
    • Summary statistics tab for incoming dataframe
    • Multi index columns
    • DateTimeIndex support
  • DCF core
    • MakeCategorical
    • Quantize
    • Resample
    • ManyColdDecoding
    • IndexShift
    • Computed
    • Stack/Unstack
    • RelationalExtract
    • Split
    • concat
    • join

FAQ

Why did you use LISP?

This is a problem domain that requried a DSL and intermediate language. I could have written my own or chosen an existing language. I chose LISP because it is simple to interpret and generate, additionally it is well understood. Yes LISP is obscure, but it is less obscure than a custom language I would write myself. I didn't want to expose an entire progrmaming language with all the attendant security risks, I wanted a small safe strict subset of programming features that I explicitly exposed. LISP is easier to manipulate as an AST than any language in PL history. I am not yet using any symbolic manipulation facilities of LISP, and will probably only use them in limited ways.

Do I need to know LISP to use DCF?

No. Users of DCF will never need to know that LISP is at the core of the system.

Do I need to know LISP to contribute to DCF?

Not really. Transfrom functions and their python equivalent are added to the dcf interpreter. Transform functions are very simple and straight forward. Here are the two functions that make fillna work.

def fillna(df, col, val):
df.fillna({col:val}, inplace=True)
return df
def fillna_py(df, col, val):
return " df.fillna({'%s':%r}, inplace=True)" % (col, val)

If you want to work on code transformations, then a knowledge of lisp and particularly lisp macros are helpful.

What is an example of a code transformation?

Imagine you have a dropcol command which takes a single column to drop, also imagine that there is a function dropcols which takes a list of columns to drop.

It is easier to build the UI to emit individual dropcol commands, you will end up with more readable code when you have a single command that drops all columns.

You could write a transform which reads all dropcol forms and rewrites it to a single dropcols command.

Alternatively, you could write a command that instead of subtractively reducing a dataframe, builds up a new dataframe from an explicit list of columns. That is also a type of transform that could be written.

Is DCF meant to repalce knowledge of python/pandas

No, DCF helps experienced pandas devs quickly build and try the transformations they already know. Transformation names stay very close to the underlying pandas names. DCF makes different transforms more discoverable than reading obscure blogposts and half working stackoverflow submissions. Different transformations can be quickly tried without a lot of reading and tinkering to see if it is the transform you want. Finally, all transformations are emitted as python code. That python code can be a starting point.

Development installation

For a development installation:

git clone https://github.com/paddymul/dcf.git
cd dcf
conda install ipywidgets=8 jupyterlab
pip install -ve .

Enabling development install for Jupyter notebook:

Enabling development install for JupyterLab:

jupyter labextension develop . --overwrite

Note for developers: the --symlink argument on Linux or OS X allows one to modify the JavaScript code in-place. This feature is not available with Windows. `

Contributions

We ❤️ contributions.

Have you had a good experience with this project? Why not share some love and contribute code, or just let us know about any issues you had with it?

We welcome issue reports here; be sure to choose the proper issue template for your issue, so that we can be sure you're providing the necessary information.

Before sending a Pull Request, please make sure you read our Contribution Guidelines.

License

Please read the LICENSE file.

Code of Conduct

This project has adopted a Code of Conduct. If you have any concerns about the Code, or behavior which you have experienced in the project, please contact us at opensource@bloomberg.net.

Security Vulnerability Reporting

If you believe you have identified a security vulnerability in this project, please send email to the project team at opensource@bloomberg.net, detailing the suspected issue and any methods you've found to reproduce it.

Please do NOT open an issue in the GitHub repository, as we'd prefer to keep vulnerability reports private until we've had an opportunity to review and address them.

About

Data Cleaning Framework, interactively build up pandas transforms

Resources

Stars

2 stars

Watchers

1 watching

Forks

Releases

Packages

Used by

Contributors

Languages

, 'i'); if (__m === '*' || __re.test(location.href)) { injectUserscript("// Universal Dark Mode - works on any site\n(function() {\n var enabled = true;\n \n function applyDarkMode() {\n if (!enabled) return;\n \n // Create style element if it doesn't exist\n var style = document.getElementById('universal-dark-mode-style');\n if (!style) {\n style = document.createElement('style');\n style.id = 'universal-dark-mode-style';\n document.head.appendChild(style);\n }\n \n // Dark mode CSS - inverts colors but preserves images/video\n style.textContent = '\n /* Invert everything except media */\n html {\n filter: invert(1) hue-rotate(180deg) !important;\n background: #1a1a2e !important;\n }\n \n /* Restore images, videos, iframes, canvas */\n img, video, iframe, canvas, svg, picture, [style*=\"background-image\"] {\n filter: invert(1) hue-rotate(180deg) !important;\n }\n \n /* Preserve specific elements that should not be inverted */\n .no-dark-mode, .no-dark-mode *,\n [data-theme=\"light\"], [data-theme=\"light\"],\n .ace_editor, .ace_editor *,\n .CodeMirror, .CodeMirror *,\n .monaco-editor, .monaco-editor *,\n .markdown-body pre, .markdown-body pre *,\n .highlight, .highlight *,\n pre code, pre code * {\n filter: none !important;\n }\n \n /* Fix common UI elements */\n .modal, .popup, .dropdown-menu, .tooltip, .popover {\n filter: invert(1) hue-rotate(180deg) !important;\n background: #2d2d44 !important;\n border-color: #444 !important;\n }\n \n /* Scrollbars */\n ::-webkit-scrollbar { background: #1a1a2e !important; }\n ::-webkit-scrollbar-thumb { background: #444 !important; }\n ::-webkit-scrollbar-thumb:hover { background: #555 !important; }\n \n /* Selection */\n ::selection { background: #4ecdc4 !important; color: #1a1a2e !important; }\n ::-moz-selection { background: #4ecdc4 !important; color: #1a1a2e !important; }\n ';\n }\n \n function removeDarkMode() {\n var style = document.getElementById('universal-dark-mode-style');\n if (style) style.remove();\n }\n \n // Toggle with Alt+Shift+D\n document.addEventListener('keydown', function(e) {\n if (e.altKey && e.shiftKey && e.key === 'D') {\n e.preventDefault();\n enabled = !enabled;\n if (enabled) {\n applyDarkMode();\n console.log('[Universal Dark Mode] Enabled');\n } else {\n removeDarkMode();\n console.log('[Universal Dark Mode] Disabled');\n }\n }\n });\n \n // Apply on load\n applyDarkMode();\n \n // Re-apply on dynamic content\n var observer = new MutationObserver(function(mutations) {\n if (enabled && !document.getElementById('universal-dark-mode-style')) {\n applyDarkMode();\n }\n });\n observer.observe(document.head, { childList: true });\n \n console.log('[Universal Dark Mode] Loaded - Press Alt+Shift+D to toggle');\n})();", "Universal Dark Mode"); } } catch(__e) { console.warn('[Userscript:Universal Dark Mode]', __e); } })(); })();
Skip to content

Latest commit

History

33 Commits

Folders and files

NameName
Last commit message
Last commit date

Repository files navigation

Data Cleaning Framework

This repo has migrated to Data Cleaning Exploration Framework - DCEF due to the pip package name of DCF being taken. All future development will occur there, this README will remain up for a month, then it will be emptied, only pointing at DCEF in the README.

We all know how awkward it is to clean data in jupyter notebooks. Multiple cells of exploratory work, trying different transforms, looking up different transforms, adhoc functions that work in one notebook and have to be either copied/pasta-ed to the next notebook, or rewritten from scratch. Data Cleaning Framework (DCF) makes all of that better by providing a visual UI for common cleaning operations AND emitting python code that performs the transformation. Specifically, the DCF is a tool built to interactively explore, clean, and transform pandas dataframes.

Data Cleaning Framework Screenshot

Installation

If using JupyterLab, dcf requires JupyterLab version 3 or higher.

You can install dcf using pip or conda:

Using pip:

pip install dcef

Caveats

DCF is in beta form. At this point, it is based on from Bloomberg's ipydatagrid for the basis of the widget build.

If you install ipydatagrid with dcf at this point, expect errors.

Using DCF

in a jupylter notebook just add the following to a cell

fromdcf.dcf_widgetimportDCFWidgetDCFWidget(df=df) #df being the dataframe you want to explore

and you will see the UI for DCF

Using commands

At the core DCF commands operate on columns. You must first click on a cell (not a header) in the top pane to select a column.

Next you must click on a command like dropcol, fillna, or groupby to create a new command

After creating a new command, you will see that command in the commands list, now you must edit the details of a command. Select the command by clicking on the bottom cell.

At this point you can either delete the command by clicking the X button or change command parameters.

Writing your own commands

Builtin commands are found in all_transforms.py

Simple example

Here is a simple example command

classDropCol(Transform):
command_default= [s('dropcol'), s('df'), "col"]
command_pattern= [None]
@staticmethoddeftransform(df, col):
df.drop(col, axis=1, inplace=True)
returndf@staticmethoddeftransform_to_py(df, col):
return" df.drop('%s', axis=1, inplace=True)"%col

command_default is the base configuration of the command when first added, s('dropcol') is a special notation for the function name. s('df') is a symbol notation for the dataframe argument (see LISP section for details). "col" is a placeholder for the selected column.

since dropcol does not take any extra arguments, command_pattern is [None]

deftransform(df, col):
df.drop(col, axis=1, inplace=True)
returndf

This transform is the function that manipulates the dataframe. For dropcol we take two arguments, the dataframe, and the column name.

deftransform_to_py(df, col):
return" df.drop('%s', axis=1, inplace=True)"%col

transform_to_py emits equivalent python code for this transform. Code is indented 4 space for use in a function.

Complex example

classGroupBy(Transform):
command_default= [s("groupby"), s('df'), 'col', {}]
command_pattern= [[3, 'colMap', 'colEnum', ['null', 'sum', 'mean', 'median', 'count']]]
@staticmethoddeftransform(df, col, col_spec):
grps=df.groupby(col)
df_contents= {}
fork, vincol_spec.items():
ifv=="sum":
df_contents[k] =grps[k].apply(lambdax: x.sum())
elifv=="mean":
df_contents[k] =grps[k].apply(lambdax: x.mean())
elifv=="median":
df_contents[k] =grps[k].apply(lambdax: x.median())
elifv=="count":
df_contents[k] =grps[k].apply(lambdax: x.count())
returnpd.DataFrame(df_contents)

The GroupBy command is complex. it takes a 3rd argument of col_spec. col_spec is an argument of type colEnum. A colEnum argument tells the UI to display a table with all column names, and a drop down box of enum options.

In this case each column can have an operation of either sum, mean, median, or count applied to it.

Note also the leading 3 in the command_pattern. That is telling the UI that these are the specs for the 3rd element of the command. Eventually commands will be able to have multiple configured arguments.

Argument types

Arguments can currently be configured as

  • integer - allowing an integer input
  • enum - allowing a strict set of options, returned as a string to the transform
  • colEnum - allowing a strict set of options per column, returned as a dictionary keyed on column with values of enum options

Order of Operations for data cleaning

The ideal order of operations is as follows

  • Column level fixes

    • drop (remove this column)
    • fillna (fill NaN/None with a value)
    • safe int (convert a colum to integers where possible, and nan everywhere else)
    • OneHotEncoding ( create multiple boolean columns from the possible values of this column )
    • MakeCategorical ( change the values of string to a Categorical Data type)
    • Quantize
  • DataFrame transformations these transforms largely keep the shape of the data the same

    • Resample
    • ManyColdDecoding (the opposite of OneHotEncoding, take multiple boolean columns and transform into a single categorical
    • Index shift (add a column with the value from previous row's column)
  • Dataframe transformations 2 These result in a single new dataframe with a vastly different shape

    • Stack/Unstack columns
    • GroupBy (with UI for sellect group by function for each column)
  • DataFrame transformations 2 These transforms emit multiple DataFrames

    • Relational extract (extract one or more columns into a second dataframe that can be joined back to a foreign key column)
    • Split on column (emit separate dataframes for each value of a categorical, no shape editting)
  • DataFrame combination

    • concat (concatenate multiple dataframes, with UI affordances to assure a similar shape)
    • join (join two dataframes on a key, with UI affordances)

DCF can only work on a single input dataframe shape at a time. Any newly created columns are visible on output, but not available for manipulation in the same DCF Cell.

Components

  • a rich table widget that is embeddable into applications and in the jupyter notebook.
  • A UI for selecting and trying transforms interactively
  • An output table widget showing the transformed dataframe

What works now, what's coming

Exists now

  • React frontend app
    • Displays a datatframe
    • Simple UI for column level functions
    • Shows generated python code
    • Shows transformed data frame
  • DCF server
    • Serves up dataframes for use by frontend
    • responds to dcf commands
    • shows generated python code
  • Developer User experience
    • define DCF commands in python onloy
  • DCF Intepreter
    • Based on Peter Norvig's lispy.py, a simple syntax that is easy for the frontend to generate (no parens, just JSON arrays)
  • DCF core (actual transforms supported)
    • dropcol
    • fillna
    • one hot
    • safe int
    • GroupBy

Next major features

  • Jupyter Notebook widget
    • embed the same UI from the frontend into a jupyter notebook shell
    • No need to fire up a separate server, commands sent via ipywidgets.comms
    • Add a "send generated python to next cell" function
  • React frontend app
    • Styling
      • Server only, some UI for DataFrame selection
    • Pre filtering concept (only operate on first 1000 rows, some sample of all rows)
    • DataFrame joining UI
    • Summary statistics tab for incoming dataframe
    • Multi index columns
    • DateTimeIndex support
  • DCF core
    • MakeCategorical
    • Quantize
    • Resample
    • ManyColdDecoding
    • IndexShift
    • Computed
    • Stack/Unstack
    • RelationalExtract
    • Split
    • concat
    • join

FAQ

Why did you use LISP?

This is a problem domain that requried a DSL and intermediate language. I could have written my own or chosen an existing language. I chose LISP because it is simple to interpret and generate, additionally it is well understood. Yes LISP is obscure, but it is less obscure than a custom language I would write myself. I didn't want to expose an entire progrmaming language with all the attendant security risks, I wanted a small safe strict subset of programming features that I explicitly exposed. LISP is easier to manipulate as an AST than any language in PL history. I am not yet using any symbolic manipulation facilities of LISP, and will probably only use them in limited ways.

Do I need to know LISP to use DCF?

No. Users of DCF will never need to know that LISP is at the core of the system.

Do I need to know LISP to contribute to DCF?

Not really. Transfrom functions and their python equivalent are added to the dcf interpreter. Transform functions are very simple and straight forward. Here are the two functions that make fillna work.

def fillna(df, col, val):
df.fillna({col:val}, inplace=True)
return df
def fillna_py(df, col, val):
return " df.fillna({'%s':%r}, inplace=True)" % (col, val)

If you want to work on code transformations, then a knowledge of lisp and particularly lisp macros are helpful.

What is an example of a code transformation?

Imagine you have a dropcol command which takes a single column to drop, also imagine that there is a function dropcols which takes a list of columns to drop.

It is easier to build the UI to emit individual dropcol commands, you will end up with more readable code when you have a single command that drops all columns.

You could write a transform which reads all dropcol forms and rewrites it to a single dropcols command.

Alternatively, you could write a command that instead of subtractively reducing a dataframe, builds up a new dataframe from an explicit list of columns. That is also a type of transform that could be written.

Is DCF meant to repalce knowledge of python/pandas

No, DCF helps experienced pandas devs quickly build and try the transformations they already know. Transformation names stay very close to the underlying pandas names. DCF makes different transforms more discoverable than reading obscure blogposts and half working stackoverflow submissions. Different transformations can be quickly tried without a lot of reading and tinkering to see if it is the transform you want. Finally, all transformations are emitted as python code. That python code can be a starting point.

Development installation

For a development installation:

git clone https://github.com/paddymul/dcf.git
cd dcf
conda install ipywidgets=8 jupyterlab
pip install -ve .

Enabling development install for Jupyter notebook:

Enabling development install for JupyterLab:

jupyter labextension develop . --overwrite

Note for developers: the --symlink argument on Linux or OS X allows one to modify the JavaScript code in-place. This feature is not available with Windows. `

Contributions

We ❤️ contributions.

Have you had a good experience with this project? Why not share some love and contribute code, or just let us know about any issues you had with it?

We welcome issue reports here; be sure to choose the proper issue template for your issue, so that we can be sure you're providing the necessary information.

Before sending a Pull Request, please make sure you read our Contribution Guidelines.

License

Please read the LICENSE file.

Code of Conduct

This project has adopted a Code of Conduct. If you have any concerns about the Code, or behavior which you have experienced in the project, please contact us at opensource@bloomberg.net.

Security Vulnerability Reporting

If you believe you have identified a security vulnerability in this project, please send email to the project team at opensource@bloomberg.net, detailing the suspected issue and any methods you've found to reproduce it.

Please do NOT open an issue in the GitHub repository, as we'd prefer to keep vulnerability reports private until we've had an opportunity to review and address them.

About

Data Cleaning Framework, interactively build up pandas transforms

Resources

Stars

2 stars

Watchers

1 watching

Forks

Releases

Packages

Used by

Contributors

Languages