field-match 0.1.0__tar.gz

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
Binary file
@@ -0,0 +1,27 @@
1
+ name: continuous-integration
2
+
3
+ on:
4
+ push:
5
+ branches:
6
+ - main
7
+
8
+ jobs:
9
+ test:
10
+ runs-on: ubuntu-latest
11
+
12
+ steps:
13
+ - name: Checkout repository
14
+ uses: actions/checkout@v2
15
+
16
+ - name: Set up Python
17
+ uses: actions/setup-python@v2
18
+ with:
19
+ python-version: 3.8
20
+
21
+ - name: Run tests
22
+ run: |
23
+ python -m venv venv
24
+ source venv/bin/activate
25
+ pip install --upgrade pip
26
+ pip install -r requirements.txt
27
+ python -m unittest discover -s tests
@@ -0,0 +1,160 @@
1
+ # Byte-compiled / optimized / DLL files
2
+ __pycache__/
3
+ *.py[cod]
4
+ *$py.class
5
+
6
+ # C extensions
7
+ *.so
8
+
9
+ # Distribution / packaging
10
+ .Python
11
+ build/
12
+ develop-eggs/
13
+ dist/
14
+ downloads/
15
+ eggs/
16
+ .eggs/
17
+ lib/
18
+ lib64/
19
+ parts/
20
+ sdist/
21
+ var/
22
+ wheels/
23
+ share/python-wheels/
24
+ *.egg-info/
25
+ .installed.cfg
26
+ *.egg
27
+ MANIFEST
28
+
29
+ # PyInstaller
30
+ # Usually these files are written by a python script from a template
31
+ # before PyInstaller builds the exe, so as to inject date/other infos into it.
32
+ *.manifest
33
+ *.spec
34
+
35
+ # Installer logs
36
+ pip-log.txt
37
+ pip-delete-this-directory.txt
38
+
39
+ # Unit test / coverage reports
40
+ htmlcov/
41
+ .tox/
42
+ .nox/
43
+ .coverage
44
+ .coverage.*
45
+ .cache
46
+ nosetests.xml
47
+ coverage.xml
48
+ *.cover
49
+ *.py,cover
50
+ .hypothesis/
51
+ .pytest_cache/
52
+ cover/
53
+
54
+ # Translations
55
+ *.mo
56
+ *.pot
57
+
58
+ # Django stuff:
59
+ *.log
60
+ local_settings.py
61
+ db.sqlite3
62
+ db.sqlite3-journal
63
+
64
+ # Flask stuff:
65
+ instance/
66
+ .webassets-cache
67
+
68
+ # Scrapy stuff:
69
+ .scrapy
70
+
71
+ # Sphinx documentation
72
+ docs/_build/
73
+
74
+ # PyBuilder
75
+ .pybuilder/
76
+ target/
77
+
78
+ # Jupyter Notebook
79
+ .ipynb_checkpoints
80
+
81
+ # IPython
82
+ profile_default/
83
+ ipython_config.py
84
+
85
+ # pyenv
86
+ # For a library or package, you might want to ignore these files since the code is
87
+ # intended to run in multiple environments; otherwise, check them in:
88
+ # .python-version
89
+
90
+ # pipenv
91
+ # According to pypa/pipenv#598, it is recommended to include Pipfile.lock in version control.
92
+ # However, in case of collaboration, if having platform-specific dependencies or dependencies
93
+ # having no cross-platform support, pipenv may install dependencies that don't work, or not
94
+ # install all needed dependencies.
95
+ #Pipfile.lock
96
+
97
+ # poetry
98
+ # Similar to Pipfile.lock, it is generally recommended to include poetry.lock in version control.
99
+ # This is especially recommended for binary packages to ensure reproducibility, and is more
100
+ # commonly ignored for libraries.
101
+ # https://python-poetry.org/docs/basic-usage/#commit-your-poetrylock-file-to-version-control
102
+ #poetry.lock
103
+
104
+ # pdm
105
+ # Similar to Pipfile.lock, it is generally recommended to include pdm.lock in version control.
106
+ #pdm.lock
107
+ # pdm stores project-wide configurations in .pdm.toml, but it is recommended to not include it
108
+ # in version control.
109
+ # https://pdm.fming.dev/#use-with-ide
110
+ .pdm.toml
111
+
112
+ # PEP 582; used by e.g. github.com/David-OConnor/pyflow and github.com/pdm-project/pdm
113
+ __pypackages__/
114
+
115
+ # Celery stuff
116
+ celerybeat-schedule
117
+ celerybeat.pid
118
+
119
+ # SageMath parsed files
120
+ *.sage.py
121
+
122
+ # Environments
123
+ .env
124
+ .venv
125
+ env/
126
+ venv/
127
+ ENV/
128
+ env.bak/
129
+ venv.bak/
130
+
131
+ # Spyder project settings
132
+ .spyderproject
133
+ .spyproject
134
+
135
+ # Rope project settings
136
+ .ropeproject
137
+
138
+ # mkdocs documentation
139
+ /site
140
+
141
+ # mypy
142
+ .mypy_cache/
143
+ .dmypy.json
144
+ dmypy.json
145
+
146
+ # Pyre type checker
147
+ .pyre/
148
+
149
+ # pytype static type analyzer
150
+ .pytype/
151
+
152
+ # Cython debug symbols
153
+ cython_debug/
154
+
155
+ # PyCharm
156
+ # JetBrains specific template is maintained in a separate JetBrains.gitignore that can
157
+ # be found at https://github.com/github/gitignore/blob/main/Global/JetBrains.gitignore
158
+ # and can be added to the global gitignore or merged into this file. For a more nuclear
159
+ # option (not recommended) you can uncomment the following to ignore the entire idea folder.
160
+ #.idea/
File without changes
@@ -0,0 +1,12 @@
1
+ <?xml version="1.0" encoding="UTF-8"?>
2
+ <module type="PYTHON_MODULE" version="4">
3
+ <component name="NewModuleRootManager">
4
+ <content url="file://$MODULE_DIR$" />
5
+ <orderEntry type="inheritedJdk" />
6
+ <orderEntry type="sourceFolder" forTests="false" />
7
+ </component>
8
+ <component name="PyDocumentationSettings">
9
+ <option name="format" value="GOOGLE" />
10
+ <option name="myDocStringFormat" value="Google" />
11
+ </component>
12
+ </module>
@@ -0,0 +1,6 @@
1
+ <component name="InspectionProjectProfileManager">
2
+ <settings>
3
+ <option name="USE_PROJECT_PROFILE" value="false" />
4
+ <version value="1.0" />
5
+ </settings>
6
+ </component>
@@ -0,0 +1,4 @@
1
+ <?xml version="1.0" encoding="UTF-8"?>
2
+ <project version="4">
3
+ <component name="ProjectRootManager" version="2" project-jdk-name="Python 3.8 (field-match)" project-jdk-type="Python SDK" />
4
+ </project>
@@ -0,0 +1,8 @@
1
+ <?xml version="1.0" encoding="UTF-8"?>
2
+ <project version="4">
3
+ <component name="ProjectModuleManager">
4
+ <modules>
5
+ <module fileurl="file://$PROJECT_DIR$/.idea/field-match.iml" filepath="$PROJECT_DIR$/.idea/field-match.iml" />
6
+ </modules>
7
+ </component>
8
+ </project>
@@ -0,0 +1,6 @@
1
+ <?xml version="1.0" encoding="UTF-8"?>
2
+ <project version="4">
3
+ <component name="VcsDirectoryMappings">
4
+ <mapping directory="$PROJECT_DIR$" vcs="Git" />
5
+ </component>
6
+ </project>
@@ -0,0 +1,181 @@
1
+ <?xml version="1.0" encoding="UTF-8"?>
2
+ <project version="4">
3
+ <component name="ChangeListManager">
4
+ <list default="true" id="eb64838e-f39e-4f99-94ef-8fb088601c45" name="Changes" comment="Update CI">
5
+ <change afterPath="$PROJECT_DIR$/.github/workflows/ci.yml" afterDir="false" />
6
+ <change afterPath="$PROJECT_DIR$/LICENSE" afterDir="false" />
7
+ <change afterPath="$PROJECT_DIR$/examples/usage.py" afterDir="false" />
8
+ <change afterPath="$PROJECT_DIR$/pyproject.toml" afterDir="false" />
9
+ <change afterPath="$PROJECT_DIR$/requirements.txt" afterDir="false" />
10
+ <change afterPath="$PROJECT_DIR$/src/field_match/__init__.py" afterDir="false" />
11
+ <change afterPath="$PROJECT_DIR$/src/field_match/field_match.py" afterDir="false" />
12
+ <change afterPath="$PROJECT_DIR$/tests/__init__.py" afterDir="false" />
13
+ <change afterPath="$PROJECT_DIR$/tests/test_field_match.py" afterDir="false" />
14
+ <change beforePath="$PROJECT_DIR$/README.md" beforeDir="false" afterPath="$PROJECT_DIR$/README.md" afterDir="false" />
15
+ </list>
16
+ <option name="SHOW_DIALOG" value="false" />
17
+ <option name="HIGHLIGHT_CONFLICTS" value="true" />
18
+ <option name="HIGHLIGHT_NON_ACTIVE_CHANGELIST" value="false" />
19
+ <option name="LAST_RESOLUTION" value="IGNORE" />
20
+ </component>
21
+ <component name="FileTemplateManagerImpl">
22
+ <option name="RECENT_TEMPLATES">
23
+ <list>
24
+ <option value="Python Script" />
25
+ </list>
26
+ </option>
27
+ </component>
28
+ <component name="Git.Settings">
29
+ <option name="PREVIOUS_COMMIT_AUTHORS">
30
+ <list>
31
+ <option value="cstirry &lt;christine.stirrat@gmail.com&gt;" />
32
+ <option value="Christine Stirrat &lt;christine.stirrat@gmail.com&gt;" />
33
+ </list>
34
+ </option>
35
+ <option name="RECENT_GIT_ROOT_PATH" value="$PROJECT_DIR$" />
36
+ </component>
37
+ <component name="MarkdownSettingsMigration">
38
+ <option name="stateVersion" value="1" />
39
+ </component>
40
+ <component name="ProblemsViewState">
41
+ <option name="selectedTabId" value="ProjectErrors" />
42
+ </component>
43
+ <component name="ProjectId" id="2aegJrJPG76FhHan0nc8AGOXlKC" />
44
+ <component name="ProjectLevelVcsManager" settingsEditedManually="true" />
45
+ <component name="ProjectViewState">
46
+ <option name="hideEmptyMiddlePackages" value="true" />
47
+ <option name="showLibraryContents" value="true" />
48
+ </component>
49
+ <component name="PropertiesComponent"><![CDATA[{
50
+ "keyToString": {
51
+ "ASKED_ADD_EXTERNAL_FILES": "true",
52
+ "ASKED_SHARE_PROJECT_CONFIGURATION_FILES": "true",
53
+ "RunOnceActivity.OpenProjectViewOnStart": "true",
54
+ "RunOnceActivity.ShowReadmeOnStart": "true",
55
+ "TODO_SCOPE": "All Places",
56
+ "last_opened_file_path": "/Users/christinestirrat/field-match",
57
+ "run.code.analysis.last.selected.profile": "pProject Default",
58
+ "settings.editor.selected.configurable": "com.jetbrains.python.configuration.PyActiveSdkModuleConfigurable"
59
+ }
60
+ }]]></component>
61
+ <component name="RecentsManager">
62
+ <key name="CopyFile.RECENT_KEYS">
63
+ <recent name="$PROJECT_DIR$" />
64
+ </key>
65
+ <key name="MoveFile.RECENT_KEYS">
66
+ <recent name="$PROJECT_DIR$/field_match" />
67
+ <recent name="$PROJECT_DIR$/src/field_match" />
68
+ </key>
69
+ </component>
70
+ <component name="RunManager" selected="Python.test_field_match">
71
+ <configuration name="field_match" type="PythonConfigurationType" factoryName="Python" temporary="true" nameIsGenerated="true">
72
+ <module name="field-match" />
73
+ <option name="INTERPRETER_OPTIONS" value="" />
74
+ <option name="PARENT_ENVS" value="true" />
75
+ <envs>
76
+ <env name="PYTHONUNBUFFERED" value="1" />
77
+ </envs>
78
+ <option name="SDK_HOME" value="" />
79
+ <option name="WORKING_DIRECTORY" value="$PROJECT_DIR$/src/field_match" />
80
+ <option name="IS_MODULE_SDK" value="true" />
81
+ <option name="ADD_CONTENT_ROOTS" value="true" />
82
+ <option name="ADD_SOURCE_ROOTS" value="true" />
83
+ <option name="SCRIPT_NAME" value="$PROJECT_DIR$/field_match/field_match.py" />
84
+ <option name="PARAMETERS" value="" />
85
+ <option name="SHOW_COMMAND_LINE" value="false" />
86
+ <option name="EMULATE_TERMINAL" value="false" />
87
+ <option name="MODULE_MODE" value="false" />
88
+ <option name="REDIRECT_INPUT" value="false" />
89
+ <option name="INPUT_FILE" value="" />
90
+ <method v="2" />
91
+ </configuration>
92
+ <configuration name="test_field_match" type="PythonConfigurationType" factoryName="Python" temporary="true" nameIsGenerated="true">
93
+ <module name="field-match" />
94
+ <option name="INTERPRETER_OPTIONS" value="" />
95
+ <option name="PARENT_ENVS" value="true" />
96
+ <envs>
97
+ <env name="PYTHONUNBUFFERED" value="1" />
98
+ </envs>
99
+ <option name="SDK_HOME" value="" />
100
+ <option name="WORKING_DIRECTORY" value="$PROJECT_DIR$/tests" />
101
+ <option name="IS_MODULE_SDK" value="true" />
102
+ <option name="ADD_CONTENT_ROOTS" value="true" />
103
+ <option name="ADD_SOURCE_ROOTS" value="true" />
104
+ <option name="SCRIPT_NAME" value="$PROJECT_DIR$/tests/test_field_match.py" />
105
+ <option name="PARAMETERS" value="" />
106
+ <option name="SHOW_COMMAND_LINE" value="false" />
107
+ <option name="EMULATE_TERMINAL" value="false" />
108
+ <option name="MODULE_MODE" value="false" />
109
+ <option name="REDIRECT_INPUT" value="false" />
110
+ <option name="INPUT_FILE" value="" />
111
+ <method v="2" />
112
+ </configuration>
113
+ <configuration name="usage" type="PythonConfigurationType" factoryName="Python" temporary="true" nameIsGenerated="true">
114
+ <module name="field-match" />
115
+ <option name="INTERPRETER_OPTIONS" value="" />
116
+ <option name="PARENT_ENVS" value="true" />
117
+ <envs>
118
+ <env name="PYTHONUNBUFFERED" value="1" />
119
+ </envs>
120
+ <option name="SDK_HOME" value="" />
121
+ <option name="WORKING_DIRECTORY" value="$PROJECT_DIR$/examples" />
122
+ <option name="IS_MODULE_SDK" value="true" />
123
+ <option name="ADD_CONTENT_ROOTS" value="true" />
124
+ <option name="ADD_SOURCE_ROOTS" value="true" />
125
+ <option name="SCRIPT_NAME" value="$PROJECT_DIR$/examples/usage.py" />
126
+ <option name="PARAMETERS" value="" />
127
+ <option name="SHOW_COMMAND_LINE" value="false" />
128
+ <option name="EMULATE_TERMINAL" value="false" />
129
+ <option name="MODULE_MODE" value="false" />
130
+ <option name="REDIRECT_INPUT" value="false" />
131
+ <option name="INPUT_FILE" value="" />
132
+ <method v="2" />
133
+ </configuration>
134
+ <recent_temporary>
135
+ <list>
136
+ <item itemvalue="Python.test_field_match" />
137
+ <item itemvalue="Python.usage" />
138
+ <item itemvalue="Python.field_match" />
139
+ </list>
140
+ </recent_temporary>
141
+ </component>
142
+ <component name="SpellCheckerSettings" RuntimeDictionaries="0" Folders="0" CustomDictionaries="0" DefaultDictionary="application-level" UseSingleDictionary="true" transferred="true" />
143
+ <component name="TaskManager">
144
+ <task active="true" id="Default" summary="Default task">
145
+ <changelist id="eb64838e-f39e-4f99-94ef-8fb088601c45" name="Changes" comment="" />
146
+ <created>1704684389781</created>
147
+ <option name="number" value="Default" />
148
+ <option name="presentableId" value="Default" />
149
+ <updated>1704684389781</updated>
150
+ </task>
151
+ <task id="LOCAL-00001" summary="Add files">
152
+ <created>1704768110097</created>
153
+ <option name="number" value="00001" />
154
+ <option name="presentableId" value="LOCAL-00001" />
155
+ <option name="project" value="LOCAL" />
156
+ <updated>1704768110097</updated>
157
+ </task>
158
+ <task id="LOCAL-00002" summary="Update CI">
159
+ <created>1704768383123</created>
160
+ <option name="number" value="00002" />
161
+ <option name="presentableId" value="LOCAL-00002" />
162
+ <option name="project" value="LOCAL" />
163
+ <updated>1704768383124</updated>
164
+ </task>
165
+ <option name="localTasksCounter" value="3" />
166
+ <servers />
167
+ </component>
168
+ <component name="UpdateCopyrightCheckinHandler">
169
+ <option name="UPDATE_COPYRIGHT" value="true" />
170
+ </component>
171
+ <component name="VcsManagerConfiguration">
172
+ <option name="CHECK_CODE_SMELLS_BEFORE_PROJECT_COMMIT" value="true" />
173
+ <option name="CHECK_CODE_CLEANUP_BEFORE_PROJECT_COMMIT" value="true" />
174
+ <MESSAGE value="Add files" />
175
+ <MESSAGE value="Update CI" />
176
+ <option name="LAST_COMMIT_MESSAGE" value="Update CI" />
177
+ <option name="OPTIMIZE_IMPORTS_BEFORE_PROJECT_COMMIT" value="true" />
178
+ <option name="REFORMAT_BEFORE_PROJECT_COMMIT" value="true" />
179
+ <option name="REARRANGE_BEFORE_PROJECT_COMMIT" value="true" />
180
+ </component>
181
+ </project>
@@ -0,0 +1,21 @@
1
+ MIT License
2
+
3
+ Copyright (c) 2024 Christine Stirrat
4
+
5
+ Permission is hereby granted, free of charge, to any person obtaining a copy
6
+ of this software and associated documentation files (the "Software"), to deal
7
+ in the Software without restriction, including without limitation the rights
8
+ to use, copy, modify, merge, publish, distribute, sublicense, and/or sell
9
+ copies of the Software, and to permit persons to whom the Software is
10
+ furnished to do so, subject to the following conditions:
11
+
12
+ The above copyright notice and this permission notice shall be included in all
13
+ copies or substantial portions of the Software.
14
+
15
+ THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
16
+ IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
17
+ FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE
18
+ AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
19
+ LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM,
20
+ OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE
21
+ SOFTWARE.
@@ -0,0 +1,66 @@
1
+ Metadata-Version: 2.1
2
+ Name: field_match
3
+ Version: 0.1.0
4
+ Summary: Identify corresponding fields between datasets
5
+ Project-URL: Homepage, https://github.com/cstirry/field-match
6
+ Author-email: Christine Stirrat <christine.stirrat@gmail.com>
7
+ License-File: LICENSE
8
+ Classifier: License :: OSI Approved :: MIT License
9
+ Classifier: Operating System :: OS Independent
10
+ Classifier: Programming Language :: Python :: 3
11
+ Requires-Python: >=3.8
12
+ Description-Content-Type: text/markdown
13
+
14
+ # field-match
15
+
16
+ ## Overview
17
+
18
+ field-match is a Python library designed to analyze and compare fields between two datasets based on similarity scores,
19
+ and match up the field names.
20
+
21
+ ## Installation
22
+
23
+ To install the package, run:
24
+
25
+ ```bash
26
+ pip install field-match
27
+ ```
28
+
29
+ ## Usage
30
+
31
+ Import and use the package as follows:
32
+
33
+ ```python
34
+ import pandas as pd
35
+ from field_match import field_similarity_report, generate_column_rename
36
+
37
+ # Load your datasets
38
+ df1 = pd.read_csv('dataset1.csv')
39
+ df2 = pd.read_csv('dataset2.csv')
40
+
41
+ # Use the field_similarity_report function to match up fields by similarity score
42
+ results = field_similarity_report(df1, df2)
43
+
44
+ # Generate the column rename code snippet
45
+ rename_snippet = generate_column_rename(results)
46
+
47
+ ```
48
+
49
+ For more detailed examples, please refer to the `examples` folder in this repository.
50
+
51
+ ## Example Applications
52
+
53
+ 1. **Clarifying Ambiguous Dataset Fields:** When merging or integrating a new or updated dataset (such as data from a
54
+ another year or source) with an existing dataset or workflow, uncertainty in how fields correspond to each
55
+ may exist because of a lack of headers or differing field names. field_match can help identify which fields in the
56
+ new
57
+ dataset correspond to expected fields.
58
+
59
+ 2. **Integrating External Dataset with Existing Model:** When feeding an external dataset into a pre-existing model,
60
+ it's crucial to ensure that the data aligns correctly with the model's expected input format. field_match can help
61
+ you
62
+ identify which fields in your new dataset correspond to the fields your model expects.
63
+
64
+ ## License
65
+
66
+ field-match is released under the [MIT License](LICENSE).
@@ -0,0 +1,53 @@
1
+ # field-match
2
+
3
+ ## Overview
4
+
5
+ field-match is a Python library designed to analyze and compare fields between two datasets based on similarity scores,
6
+ and match up the field names.
7
+
8
+ ## Installation
9
+
10
+ To install the package, run:
11
+
12
+ ```bash
13
+ pip install field-match
14
+ ```
15
+
16
+ ## Usage
17
+
18
+ Import and use the package as follows:
19
+
20
+ ```python
21
+ import pandas as pd
22
+ from field_match import field_similarity_report, generate_column_rename
23
+
24
+ # Load your datasets
25
+ df1 = pd.read_csv('dataset1.csv')
26
+ df2 = pd.read_csv('dataset2.csv')
27
+
28
+ # Use the field_similarity_report function to match up fields by similarity score
29
+ results = field_similarity_report(df1, df2)
30
+
31
+ # Generate the column rename code snippet
32
+ rename_snippet = generate_column_rename(results)
33
+
34
+ ```
35
+
36
+ For more detailed examples, please refer to the `examples` folder in this repository.
37
+
38
+ ## Example Applications
39
+
40
+ 1. **Clarifying Ambiguous Dataset Fields:** When merging or integrating a new or updated dataset (such as data from a
41
+ another year or source) with an existing dataset or workflow, uncertainty in how fields correspond to each
42
+ may exist because of a lack of headers or differing field names. field_match can help identify which fields in the
43
+ new
44
+ dataset correspond to expected fields.
45
+
46
+ 2. **Integrating External Dataset with Existing Model:** When feeding an external dataset into a pre-existing model,
47
+ it's crucial to ensure that the data aligns correctly with the model's expected input format. field_match can help
48
+ you
49
+ identify which fields in your new dataset correspond to the fields your model expects.
50
+
51
+ ## License
52
+
53
+ field-match is released under the [MIT License](LICENSE).
@@ -0,0 +1,41 @@
1
+ import pandas as pd
2
+
3
+ from field_match import field_similarity_report, generate_column_rename
4
+
5
+
6
+ def main():
7
+ # Sample data for usage example
8
+ data1 = pd.DataFrame({
9
+ 'product_id': [101, 102, 103],
10
+ 'product_name': ['Apple', 'Banana', 'Cherry'],
11
+ 'price': [1.20, 0.80, 2.50]
12
+ })
13
+
14
+ data2 = pd.DataFrame({
15
+ 'id': [1, 2],
16
+ 'item_code': [101, 202],
17
+ 'description': ['Green Apple', 'Ripe Kiwi'],
18
+ 'name': ['Apple', 'Kiwi'],
19
+ 'cost': [1.10, 0.85]
20
+ })
21
+
22
+ # Using the field_similarity_report function
23
+ similarity_report = field_similarity_report(data1, data2)
24
+ print("Field Similarity Report:")
25
+ print(similarity_report)
26
+ # {'product_id': {'data_type': 'int64', 'similar_fields': [('item_code', 0.345), ('id', 0.309)]},
27
+ # 'product_name': {'data_type': 'object', 'similar_fields': [('name', 0.167), ('description', 0.105)]},
28
+ # 'price': {'data_type': 'float64', 'similar_fields': [('cost', 0.474)]}}
29
+
30
+ # Generating a code snippet for renaming columns in data2 based on the similarity report
31
+ rename_snippet = generate_column_rename(similarity_report)
32
+ print("\nSuggested Code Snippet for Renaming Columns:")
33
+ print(rename_snippet)
34
+ # "rename_dict = {
35
+ # 'item_code': 'product_id', # Similarity Score: 0.34
36
+ # 'cost': 'price', # Similarity Score: 0.47}
37
+ # data2.rename(columns=rename_dict, inplace=True)"
38
+
39
+
40
+ if __name__ == "__main__":
41
+ main()
@@ -0,0 +1,24 @@
1
+ [build-system]
2
+ requires = ["hatchling"]
3
+ build-backend = "hatchling.build"
4
+
5
+ [tool.hatch.build.targets.wheel]
6
+ packages = ["src/field_match"]
7
+
8
+ [project]
9
+ name = "field_match"
10
+ version = "0.1.0"
11
+ authors = [
12
+ { name = "Christine Stirrat", email = "christine.stirrat@gmail.com" },
13
+ ]
14
+ description = "Identify corresponding fields between datasets"
15
+ readme = "README.md"
16
+ requires-python = ">=3.8"
17
+ classifiers = [
18
+ "Programming Language :: Python :: 3",
19
+ "License :: OSI Approved :: MIT License",
20
+ "Operating System :: OS Independent",
21
+ ]
22
+
23
+ [project.urls]
24
+ Homepage = "https://github.com/cstirry/field-match"
@@ -0,0 +1,4 @@
1
+ pandas
2
+ numpy
3
+ scikit-learn
4
+
Binary file
@@ -0,0 +1,7 @@
1
+ """Top-level package for field-match."""
2
+
3
+ __author__ = """Christine Stirrat"""
4
+ __email__ = 'christine.stirrat@gmail.com'
5
+ __version__ = '0.1.0'
6
+
7
+ from .field_match import field_similarity_report, generate_column_rename
@@ -0,0 +1,197 @@
1
+ import numpy as np
2
+ import pandas as pd
3
+ from sklearn.feature_extraction.text import TfidfVectorizer
4
+ from sklearn.metrics.pairwise import cosine_similarity
5
+
6
+
7
+ def compute_text_similarity(series_1, series_2):
8
+ """
9
+ Compute the cosine similarity between two series of text data.
10
+
11
+ This function uses TF-IDF (Term Frequency-Inverse Document Frequency) to convert
12
+ the text data into vectors and then computes the cosine similarity between these vectors.
13
+ It's a measure of similarity between two non-zero vectors of an inner product space
14
+ and is used to measure the cosine of the angle between them.
15
+
16
+ Parameters:
17
+ - series_1 (pandas.Series): A pandas Series containing text data.
18
+ - series_2 (pandas.Series): Another pandas Series containing text data to be
19
+ compared with series_1.
20
+
21
+ Returns:
22
+ - float: A single floating-point number representing the average cosine similarity
23
+ score between series_1 and series_2. The score ranges from 0 (no similarity)
24
+ to 1 (identical text content).
25
+ """
26
+ vectorizer = TfidfVectorizer(stop_words='english')
27
+ combined_text = pd.concat([series_1.fillna(''), series_2.fillna('')], ignore_index=True)
28
+ tfidf_matrix = vectorizer.fit_transform(combined_text)
29
+ return cosine_similarity(tfidf_matrix[:len(series_1)], tfidf_matrix[len(series_1):]).mean()
30
+
31
+
32
+ def compute_numeric_similarity(series_1, series_2):
33
+ """
34
+ Compute similarity for date fields based on the range of dates.
35
+
36
+ This function calculates the similarity between two date series by comparing the
37
+ range (difference between max and min dates) of each series. The similarity score
38
+ is inversely proportional to the difference in ranges between the two series.
39
+
40
+ Parameters:
41
+ - series_1 (pandas.Series): A pandas Series containing datetime data.
42
+ - series_2 (pandas.Series): Another pandas Series containing datetime data for comparison.
43
+
44
+ Returns:
45
+ - float: A similarity score ranging from 0 (no similarity) to 1 (identical ranges).
46
+ """
47
+ stats_1 = series_1.describe()
48
+ stats_2 = series_2.describe()
49
+ similarities = {
50
+ 'mean': 1 - abs(stats_1['mean'] - stats_2['mean']) / max(stats_1['mean'], stats_2['mean'], 1),
51
+ 'median': 1 - abs(stats_1['50%'] - stats_2['50%']) / max(stats_1['50%'], stats_2['50%'], 1),
52
+ 'std_dev': 1 - abs(stats_1['std'] - stats_2['std']) / max(stats_1['std'], stats_2['std'], 1),
53
+ 'range': 1 - abs((stats_1['max'] - stats_1['min']) - (stats_2['max'] - stats_2['min'])) / max(
54
+ stats_1['max'] - stats_1['min'], stats_2['max'] - stats_2['min'], 1)
55
+ }
56
+ return np.mean(list(similarities.values()))
57
+
58
+
59
+ def compute_date_similarity(series_1, series_2):
60
+ """
61
+ Compute similarity for date fields based on the range of dates.
62
+
63
+ This function calculates the similarity between two date series by comparing the
64
+ range (difference between max and min dates) of each series. The similarity score
65
+ is inversely proportional to the difference in ranges between the two series.
66
+
67
+ Parameters:
68
+ - series_1 (pandas.Series): A pandas Series containing datetime data.
69
+ - series_2 (pandas.Series): Another pandas Series containing datetime data for comparison.
70
+
71
+ Returns:
72
+ - float: A similarity score ranging from 0 (no similarity) to 1 (identical ranges).
73
+ """
74
+ range1 = series_1.max() - series_1.min()
75
+ range2 = series_2.max() - series_2.min()
76
+ return 1 - abs((range1 - range2).days) / max(range1.days, range2.days)
77
+
78
+
79
+ def compute_boolean_similarity(series_1, series_2):
80
+ """
81
+ Compute similarity for boolean fields based on value proportions.
82
+
83
+ This function assesses the similarity between two boolean series by comparing the
84
+ proportion of True values in each. It uses a close approximation to determine if the
85
+ proportions are similar.
86
+
87
+ Parameters:
88
+ - series_1 (pandas.Series): A pandas Series containing boolean data.
89
+ - series_2 (pandas.Series): Another pandas Series containing boolean data for comparison.
90
+
91
+ Returns:
92
+ - bool: True if the series are similar in their proportion of True values, False otherwise.
93
+ """
94
+ proportion_1 = series_1.mean()
95
+ proportion_2 = series_2.mean()
96
+
97
+ # Use the smaller proportion as the base for comparison to avoid division by zero
98
+ base_proportion = min(proportion_1, proportion_2, 1 - proportion_1, 1 - proportion_2)
99
+
100
+ # Avoid division by zero; if both proportions are 0 or 1, they are identical
101
+ if base_proportion == 0:
102
+ return float(proportion_1 == proportion_2)
103
+
104
+ return 1 - abs(proportion_1 - proportion_2) / base_proportion
105
+
106
+
107
+ def compute_categorical_similarity(series_1, series_2):
108
+ """
109
+ Compute similarity for categorical data using Jaccard similarity.
110
+
111
+ This function evaluates the similarity between two categorical series by calculating
112
+ the Jaccard similarity, which is the ratio of the intersection to the union of the
113
+ unique values in both series.
114
+
115
+ Parameters:
116
+ - series_1 (pandas.Series): A pandas Series containing categorical data.
117
+ - series_2 (pandas.Series): Another pandas Series containing categorical data for comparison.
118
+
119
+ Returns:
120
+ - float: The Jaccard similarity score, ranging from 0 (no similarity) to 1 (identical categories).
121
+ """
122
+ set1 = set(series_1.dropna().unique())
123
+ set2 = set(series_2.dropna().unique())
124
+ intersection = len(set1.intersection(set2))
125
+ union = len(set1.union(set2))
126
+ return intersection / union if union != 0 else 0
127
+
128
+
129
+ def field_similarity_report(data1, data2):
130
+ """
131
+ Compares fields between two pandas DataFrame objects and calculates similarity scores
132
+ for each pair of fields with the same data type.
133
+
134
+ This function iterates through each field in `data1` and compares it with every field in `data2`
135
+ of the same data type. For each field pair, it calculates a similarity score based on the
136
+ type of data they contain (e.g., text, numeric).
137
+
138
+ Parameters:
139
+ - data1 (pandas.DataFrame): The first DataFrame containing fields to be compared.
140
+ - data2 (pandas.DataFrame): The second DataFrame against which fields from data1 are compared.
141
+
142
+ Returns:
143
+ - dict: A dictionary where each key is a field name from data1, and the value is a list of tuples.
144
+ Each tuple contains a field name from data2 and its calculated similarity score with the key field.
145
+ The data type of each field from data1 is also included in the output.
146
+ """
147
+ matched_fields_similarities = {}
148
+ for field1 in data1.columns:
149
+ field_similarities = []
150
+ for field2 in data2.columns:
151
+ if data1[field1].dtype == data2[field2].dtype:
152
+ similarity_score = 0
153
+ if pd.api.types.is_string_dtype(data1[field1]):
154
+ similarity_score = compute_text_similarity(data1[field1], data2[field2])
155
+ elif pd.api.types.is_numeric_dtype(data1[field1]):
156
+ similarity_score = compute_numeric_similarity(data1[field1], data2[field2])
157
+ elif pd.api.types.is_categorical_dtype(data1[field1]):
158
+ similarity_score = compute_categorical_similarity(data1[field1], data2[field2])
159
+ elif pd.api.types.is_datetime64_any_dtype(data1[field1]):
160
+ similarity_score = compute_date_similarity(data1[field1], data2[field2])
161
+ elif pd.api.types.is_bool_dtype(data1[field1]):
162
+ similarity_score = compute_boolean_similarity(data1[field1], data2[field2])
163
+
164
+ field_similarities.append((field2, round(similarity_score, 3)))
165
+
166
+ field_similarities.sort(key=lambda x: x[1], reverse=True)
167
+ matched_fields_similarities[field1] = {
168
+ "data_type": str(data1[field1].dtype),
169
+ "similar_fields": field_similarities
170
+ }
171
+
172
+ return matched_fields_similarities
173
+
174
+
175
+ def generate_column_rename(matched_fields_similarities):
176
+ """
177
+ Generates a Python code snippet that suggests how to rename columns in the second DataFrame
178
+ based on the highest similarity scores from the field_similarity_report output.
179
+
180
+ Parameters:
181
+ - matched_fields_similarities (dict): The output dictionary from the field_similarity_report function.
182
+
183
+ Returns:
184
+ - str: A string containing the Python code snippet for renaming columns.
185
+ """
186
+ rename_instr = "rename_dict = {\n"
187
+ for field1, match_info in matched_fields_similarities.items():
188
+ similar_fields = match_info.get('similar_fields', [])
189
+ if similar_fields: # Check if there are matching fields
190
+ most_similar_field, highest_score = similar_fields[0]
191
+ # Generate instruction only if the similarity score is above a certain threshold, e.g., 0.3
192
+ if highest_score > 0.3:
193
+ rename_instr += f" '{most_similar_field}': '{field1}', # Similarity Score: {highest_score:.2f}\n"
194
+
195
+ rename_instr += "}\ndata2.rename(columns=rename_dict, inplace=True)\n"
196
+
197
+ return rename_instr
File without changes
@@ -0,0 +1,79 @@
1
+ #!/usr/bin/env python
2
+
3
+ import unittest
4
+
5
+ import pandas as pd
6
+
7
+ from src.field_match.field_match import (
8
+ compute_text_similarity,
9
+ compute_date_similarity,
10
+ compute_boolean_similarity,
11
+ compute_numeric_similarity,
12
+ compute_categorical_similarity,
13
+ field_similarity_report,
14
+ generate_column_rename
15
+ )
16
+
17
+
18
+ class TestFieldMatch(unittest.TestCase):
19
+ """Tests for `field_match` package."""
20
+
21
+ def test_compute_text_similarity(self):
22
+ series_1 = pd.Series(["apple", "banana", "cherry"])
23
+ series_2 = pd.Series(["apple pie", "banana split"])
24
+ result = compute_text_similarity(series_1, series_2)
25
+ self.assertTrue(0 <= result <= 1)
26
+
27
+ def test_compute_date_similarity(self):
28
+ series_1 = pd.to_datetime(pd.Series(["2021-01-01", "2021-01-02", "2021-01-03"]))
29
+ series_2 = pd.to_datetime(pd.Series(["2021-01-01", "2000-12-07"]))
30
+ result = compute_date_similarity(series_1, series_2)
31
+ self.assertTrue(0 <= result <= 1)
32
+
33
+ def test_compute_boolean_similarity(self):
34
+ series_1 = pd.Series([True, False, True])
35
+ series_2 = pd.Series([True, True])
36
+ result = compute_boolean_similarity(series_1, series_2)
37
+ self.assertTrue(0 <= result <= 1)
38
+
39
+ def test_compute_numeric_similarity(self):
40
+ series_1 = pd.Series([1, 2, 3])
41
+ series_2 = pd.Series([2, 3, 4])
42
+ result = compute_numeric_similarity(series_1, series_2)
43
+ self.assertTrue(0 <= result <= 1)
44
+
45
+ def test_compute_categorical_similarity(self):
46
+ series_1 = pd.Series(["cat", "dog", "bird"])
47
+ series_2 = pd.Series(["dog", "fly"])
48
+ result = compute_categorical_similarity(series_1, series_2)
49
+ self.assertTrue(0 <= result <= 1)
50
+
51
+ def test_field_similarity_report(self):
52
+ # Create sample data
53
+ data1 = pd.DataFrame({'A': ['apple', 'banana'], 'B': [1, 2]})
54
+ data2 = pd.DataFrame({'X': ['apple pie', 'banana bread'], 'Y': [2, 3]})
55
+
56
+ # Test field_similarity_report
57
+ report = field_similarity_report(data1, data2)
58
+ self.assertIsInstance(report, dict)
59
+ self.assertIn('A', report)
60
+ self.assertIn('B', report)
61
+
62
+ def test_generate_column_rename(self):
63
+ # Sample output from field_similarity_report
64
+ report = {
65
+ 'A': {'data_type': 'int64', 'similar_fields': [('D', 0.9), ('E', 0.3)]},
66
+ 'B': {'data_type': 'object', 'similar_fields': [('F', 0.6), ('G', 0.1)]},
67
+ 'C': {'data_type': 'object', 'similar_fields': [('F', 0.2), ('G', 0.1)]},
68
+ }
69
+
70
+ # Test generate_column_rename_snippet
71
+ snippet = generate_column_rename(report)
72
+ print(snippet)
73
+ self.assertIsInstance(snippet, str)
74
+ self.assertIn("'D': 'A'", snippet)
75
+ self.assertIn("'F': 'B'", snippet)
76
+
77
+
78
+ if __name__ == '__main__':
79
+ unittest.main()