qutePandas 1.0.0__tar.gz → 1.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.
Files changed (45) hide show
  1. {qutepandas-1.0.0/qutePandas.egg-info → qutepandas-1.1.0}/PKG-INFO +1 -1
  2. {qutepandas-1.0.0 → qutepandas-1.1.0}/pyproject.toml +4 -3
  3. qutepandas-1.1.0/qutePandas/apply/__init__.py +4 -0
  4. qutepandas-1.1.0/qutePandas/apply/apply.py +70 -0
  5. qutepandas-1.1.0/qutePandas/apply/apply_col.py +50 -0
  6. qutepandas-1.1.0/qutePandas/cleaning/__init__.py +6 -0
  7. qutepandas-1.1.0/qutePandas/cleaning/dropna.py +26 -0
  8. qutepandas-1.1.0/qutePandas/cleaning/dropna_col.py +28 -0
  9. qutepandas-1.1.0/qutePandas/cleaning/fillna.py +36 -0
  10. qutepandas-1.1.0/qutePandas/cleaning/remove_duplicates.py +28 -0
  11. qutepandas-1.1.0/qutePandas/core/__init__.py +9 -0
  12. qutepandas-1.1.0/qutePandas/core/connection.py +153 -0
  13. qutepandas-1.1.0/qutePandas/core/dataframe.py +121 -0
  14. qutepandas-1.1.0/qutePandas/core/display.py +183 -0
  15. qutepandas-1.1.0/qutePandas/grouping/__init__.py +4 -0
  16. qutepandas-1.1.0/qutePandas/grouping/groupby_avg.py +39 -0
  17. qutepandas-1.1.0/qutePandas/grouping/groupby_sum.py +40 -0
  18. qutepandas-1.1.0/qutePandas/indexing/__init__.py +3 -0
  19. qutepandas-1.1.0/qutePandas/indexing/iloc.py +73 -0
  20. qutepandas-1.1.0/qutePandas/indexing/loc.py +53 -0
  21. qutepandas-1.1.0/qutePandas/introspection/__init__.py +0 -0
  22. qutepandas-1.1.0/qutePandas/introspection/dtypes.py +25 -0
  23. qutepandas-1.1.0/qutePandas/io/__init__.py +4 -0
  24. qutepandas-1.1.0/qutePandas/io/from_csv.py +27 -0
  25. qutepandas-1.1.0/qutePandas/io/to_csv.py +39 -0
  26. qutepandas-1.1.0/qutePandas/joining/__init__.py +3 -0
  27. qutepandas-1.1.0/qutePandas/joining/merge.py +94 -0
  28. qutepandas-1.1.0/qutePandas/transformation/__init__.py +5 -0
  29. qutepandas-1.1.0/qutePandas/transformation/cast.py +71 -0
  30. qutepandas-1.1.0/qutePandas/transformation/drop_col.py +53 -0
  31. qutepandas-1.1.0/qutePandas/transformation/rename.py +35 -0
  32. {qutepandas-1.0.0 → qutepandas-1.1.0/qutePandas.egg-info}/PKG-INFO +1 -1
  33. qutepandas-1.1.0/qutePandas.egg-info/SOURCES.txt +42 -0
  34. {qutepandas-1.0.0 → qutepandas-1.1.0}/setup.py +1 -1
  35. qutepandas-1.0.0/qutePandas.egg-info/SOURCES.txt +0 -13
  36. {qutepandas-1.0.0 → qutepandas-1.1.0}/MANIFEST.in +0 -0
  37. {qutepandas-1.0.0 → qutepandas-1.1.0}/README.md +0 -0
  38. {qutepandas-1.0.0 → qutepandas-1.1.0}/qutePandas/__init__.py +0 -0
  39. {qutepandas-1.0.0 → qutepandas-1.1.0}/qutePandas/utils.py +0 -0
  40. {qutepandas-1.0.0 → qutepandas-1.1.0}/qutePandas.egg-info/dependency_links.txt +0 -0
  41. {qutepandas-1.0.0 → qutepandas-1.1.0}/qutePandas.egg-info/requires.txt +0 -0
  42. {qutepandas-1.0.0 → qutepandas-1.1.0}/qutePandas.egg-info/top_level.txt +0 -0
  43. {qutepandas-1.0.0 → qutepandas-1.1.0}/requirements.txt +0 -0
  44. {qutepandas-1.0.0 → qutepandas-1.1.0}/setup.cfg +0 -0
  45. {qutepandas-1.0.0 → qutepandas-1.1.0}/tests/test_utils.py +0 -0
@@ -1,6 +1,6 @@
1
1
  Metadata-Version: 2.4
2
2
  Name: qutePandas
3
- Version: 1.0.0
3
+ Version: 1.1.0
4
4
  Summary: A pandas-like library for kdb+/q using pykx
5
5
  Home-page: https://ishapatro.github.io/qutePandas/
6
6
  Author: Isha Patro
@@ -4,7 +4,7 @@ build-backend = "setuptools.build_meta"
4
4
 
5
5
  [project]
6
6
  name = "qutePandas"
7
- version = "1.0.0"
7
+ version = "1.1.0"
8
8
  authors = [
9
9
  { name="Isha Patro", email="ishapatro21@gmail.com" },
10
10
  ]
@@ -36,8 +36,9 @@ dependencies = [
36
36
  "Bug Tracker" = "https://github.com/ishapatro/qutePandas/issues"
37
37
  "Documentation" = "https://ishapatro.github.io/qutePandas/"
38
38
 
39
- [tool.setuptools]
40
- packages = ["qutePandas"]
39
+ [tool.setuptools.packages.find]
40
+ where = ["."]
41
+ include = ["qutePandas*"]
41
42
 
42
43
  [tool.setuptools.package-data]
43
44
  qutePandas = ["*.q"]
@@ -0,0 +1,4 @@
1
+ from .apply import apply
2
+ from .apply_col import apply_col
3
+
4
+ __all__ = ['apply', 'apply_col']
@@ -0,0 +1,70 @@
1
+ import pykx as kx
2
+ import pandas as pd
3
+ from ..utils import _ensure_q_table, _handle_return
4
+
5
+
6
+ def apply(df, func, axis=0, return_type='q'):
7
+ """
8
+ Applies function to DataFrame along specified axis.
9
+
10
+ Parameters
11
+ ----------
12
+ df : pandas.DataFrame or pykx.Table
13
+ Input DataFrame.
14
+ func : callable or str
15
+ Function to apply.
16
+ axis : int, default 0
17
+ Axis along which to apply function (0=columns, 1=rows).
18
+ return_type : str, default 'q'
19
+ Desired return type ('p' or 'q').
20
+
21
+ Returns
22
+ -------
23
+ pandas.DataFrame or pandas.Series or pykx.Table or pykx.K
24
+ Result of applying function.
25
+ """
26
+ try:
27
+ q_table = _ensure_q_table(df)
28
+
29
+ if len(q_table) == 0:
30
+ if axis == 1:
31
+ return _handle_return(kx.toq([]), return_type)
32
+ if not isinstance(func, str):
33
+ pdf = df if isinstance(df, pd.DataFrame) else q_table.pd()
34
+ return pdf.apply(func, axis=axis)
35
+ elif func == "sum":
36
+ cols = kx.q("cols", q_table).py()
37
+ result = kx.toq({c: 0 for c in cols})
38
+ ret = _handle_return(result, return_type)
39
+ return pd.Series(ret) if return_type == 'p' else ret
40
+
41
+ if isinstance(func, str):
42
+ if axis == 1:
43
+ result = kx.q(f"{{({func}) each x}}", q_table)
44
+ else:
45
+ result = kx.q(f"{{({func}) each flip x}}", q_table)
46
+
47
+
48
+ else:
49
+ if axis == 1:
50
+ pdf = q_table.pd()
51
+ res_list = [func(row) for _, row in pdf.iterrows()]
52
+ result = kx.toq(res_list)
53
+
54
+ else:
55
+ cols = kx.q("cols", q_table).py()
56
+
57
+ res_dict = {}
58
+ for col in cols:
59
+ col_data = kx.q(f"{{x`{col}}}", q_table).pd()
60
+ res_dict[col] = func(col_data)
61
+
62
+ result = kx.toq(res_dict)
63
+
64
+ ret = _handle_return(result, return_type)
65
+ if return_type == 'p' and isinstance(ret, dict):
66
+ return pd.Series(ret)
67
+ return ret
68
+
69
+ except Exception as e:
70
+ raise RuntimeError(f"Failed to apply function: {e}")
@@ -0,0 +1,50 @@
1
+ import pykx as kx
2
+ import pandas as pd
3
+ import numpy as np
4
+ from ..utils import _ensure_q_table, _handle_return
5
+
6
+
7
+ def apply_col(df, col, func, return_type='q'):
8
+ """
9
+ Applies function to a single column of DataFrame.
10
+
11
+ Parameters
12
+ ----------
13
+ df : pandas.DataFrame or pykx.Table
14
+ Input DataFrame.
15
+ col : str
16
+ Column name to apply function to.
17
+ func : callable or str
18
+ Function to apply to the column. If string, applied as q function string.
19
+ return_type : str, default 'q'
20
+ Desired return type ('p' or 'q').
21
+
22
+ Returns
23
+ -------
24
+ pandas.DataFrame or pykx.Table
25
+ DataFrame with function applied to specified column.
26
+ """
27
+ try:
28
+ q_table = _ensure_q_table(df)
29
+ if len(q_table) == 0:
30
+ return _handle_return(q_table, return_type)
31
+
32
+ if isinstance(func, str):
33
+ result = kx.q(f"{{update {col}:({func}) each {col} from x}}", q_table)
34
+ else:
35
+ col_data = kx.q(f"{{x`{col}}}", q_table).py()
36
+
37
+ if hasattr(col_data, 'apply'):
38
+ new_data = col_data.apply(func)
39
+ elif isinstance(col_data, list):
40
+ new_data = [func(x) for x in col_data]
41
+ else:
42
+ new_data = np.vectorize(func)(col_data)
43
+
44
+ q_new_data = kx.toq(new_data)
45
+ result = kx.q(f"{{update {col}:y from x}}", q_table, q_new_data)
46
+
47
+ return _handle_return(result, return_type)
48
+ except Exception as e:
49
+ raise RuntimeError(f"Failed to apply function to column {col}: {e}")
50
+
@@ -0,0 +1,6 @@
1
+ from .dropna import dropna
2
+ from .dropna_col import dropna_col
3
+ from .fillna import fillna
4
+ from .remove_duplicates import remove_duplicates
5
+
6
+ __all__ = ['dropna', 'dropna_col', 'fillna', 'remove_duplicates']
@@ -0,0 +1,26 @@
1
+ import pykx as kx
2
+ import pandas as pd
3
+ from ..utils import _ensure_q_table, _handle_return
4
+
5
+ def dropna(df, return_type='q'):
6
+ """
7
+ Drops any row containing null values.
8
+
9
+ Parameters
10
+ ----------
11
+ df : pandas.DataFrame or pykx.Table
12
+ Input DataFrame.
13
+ return_type : str, default 'q'
14
+ Desired return type ('p' for pandas, 'q' for kdb+).
15
+
16
+ Returns
17
+ -------
18
+ pandas.DataFrame or pykx.Table
19
+ DataFrame with null rows removed.
20
+ """
21
+ try:
22
+ q_table = _ensure_q_table(df)
23
+ result = kx.q("{select from x where not any null each value flip x}", q_table)
24
+ return _handle_return(result, return_type)
25
+ except Exception as e:
26
+ raise RuntimeError(f"Failed to dropna: {e}")
@@ -0,0 +1,28 @@
1
+ import pykx as kx
2
+ import pandas as pd
3
+ from ..utils import _ensure_q_table, _handle_return
4
+
5
+ def dropna_col(df, col, return_type='q'):
6
+ """
7
+ Drops rows where a specific column is null.
8
+
9
+ Parameters
10
+ ----------
11
+ df : pandas.DataFrame or pykx.Table
12
+ Input DataFrame.
13
+ col : str
14
+ Column name to check for nulls.
15
+ return_type : str, default 'q'
16
+ Desired return type ('p' for pandas, 'q' for kdb+).
17
+
18
+ Returns
19
+ -------
20
+ pandas.DataFrame or pykx.Table
21
+ DataFrame with filtered rows.
22
+ """
23
+ try:
24
+ q_table = _ensure_q_table(df)
25
+ result = kx.q("{[t; c] select from t where not null t c}", q_table, kx.SymbolAtom(col))
26
+ return _handle_return(result, return_type)
27
+ except Exception as e:
28
+ raise RuntimeError(f"Failed to dropna from column {col}: {e}")
@@ -0,0 +1,36 @@
1
+ import pykx as kx
2
+ import pandas as pd
3
+ from ..utils import _ensure_q_table, _handle_return
4
+
5
+ def fillna(df, values, return_type='q'):
6
+ """
7
+ Fills null values using a dictionary mapping of columns to fill values.
8
+
9
+ Parameters
10
+ ----------
11
+ df : pandas.DataFrame or pykx.Table
12
+ Input DataFrame.
13
+ values : dict
14
+ Mapping of column names to fill values.
15
+ return_type : str, default 'q'
16
+ Desired return type ('p' or 'q').
17
+
18
+ Returns
19
+ -------
20
+ pandas.DataFrame or pykx.Table
21
+ DataFrame with nulls filled.
22
+ """
23
+ try:
24
+ if not isinstance(values, dict):
25
+ raise ValueError("values must be a dictionary")
26
+
27
+ q_table = _ensure_q_table(df)
28
+ result = q_table
29
+
30
+ for col, val in values.items():
31
+ fill_val = f'`{val}' if isinstance(val, str) else str(val)
32
+ result = kx.q(f"{{update {col}:{fill_val}^{col} from x}}", result)
33
+
34
+ return _handle_return(result, return_type)
35
+ except Exception as e:
36
+ raise RuntimeError(f"Failed to fillna: {e}")
@@ -0,0 +1,28 @@
1
+ import pykx as kx
2
+ import pandas as pd
3
+ from ..utils import _ensure_q_table, _handle_return
4
+
5
+
6
+ def remove_duplicates(df, return_type='q'):
7
+ """
8
+ Removes duplicate rows from the DataFrame, keeping the first occurrence.
9
+
10
+ Parameters
11
+ ----------
12
+ df : pandas.DataFrame or pykx.Table
13
+ Input DataFrame.
14
+ return_type : str, default 'q'
15
+ Desired return type ('p' or 'q').
16
+
17
+ Returns
18
+ -------
19
+ pandas.DataFrame or pykx.Table
20
+ DataFrame with duplicate rows removed.
21
+ """
22
+ try:
23
+ q_table = _ensure_q_table(df)
24
+ result = kx.q("{distinct x}", q_table)
25
+ return _handle_return(result, return_type)
26
+ except Exception as e:
27
+ raise RuntimeError(f"Failed to remove duplicates from table: {e}")
28
+
@@ -0,0 +1,9 @@
1
+ """
2
+ Core functionality for qutePandas - DataFrame creation and basic operations.
3
+ """
4
+
5
+ from .dataframe import DataFrame
6
+ from .display import py, np, pd, pa, pt, print
7
+ from .connection import connect, get_license_info
8
+
9
+ __all__ = ['DataFrame', 'py', 'np', 'pd', 'pa', 'pt', 'print', 'connect', 'get_license_info']
@@ -0,0 +1,153 @@
1
+ """
2
+ Connection and license management for qutePandas.
3
+ Enforces strict license validation with fail-fast behavior.
4
+ """
5
+
6
+ import os
7
+ import pykx as kx
8
+ import base64
9
+ import shutil
10
+
11
+
12
+ def _get_project_lic_dir():
13
+ """
14
+ Get the project-local license directory.
15
+ """
16
+ root = os.path.abspath(os.path.join(os.path.dirname(__file__), "..", ".."))
17
+ return os.path.join(root, "kdb_lic")
18
+
19
+
20
+ def install_license(content, is_base64=True):
21
+ """
22
+ Installs a kdb+ license from base64 content or file path.
23
+
24
+ Parameters
25
+ ----------
26
+ content : str
27
+ License content (base64) or file path.
28
+ is_base64 : bool, default True
29
+ Whether content is base64 encoded.
30
+
31
+ Returns
32
+ -------
33
+ bool
34
+ True if license installation was successful.
35
+ """
36
+ target_dir = _get_project_lic_dir()
37
+ if not os.path.exists(target_dir):
38
+ target_dir = os.path.expanduser("~/.qutepandas")
39
+
40
+ os.makedirs(target_dir, exist_ok=True)
41
+ target_path = os.path.join(target_dir, "kc.lic")
42
+
43
+ content = content.strip().strip('"').strip("'")
44
+
45
+ if is_base64:
46
+ padding = len(content) % 4
47
+ if padding:
48
+ content += "=" * (4 - padding)
49
+ content = content.replace('-', '+').replace('_', '/')
50
+
51
+ try:
52
+ data = base64.b64decode(content)
53
+ except Exception as e:
54
+ raise RuntimeError(f"Invalid base64 license content: {e}")
55
+
56
+ try:
57
+ text_data = data.decode('utf-8')
58
+ if text_data.startswith('0001'):
59
+ os.environ['KX_TOKEN'] = text_data
60
+ os.environ['QTOK'] = text_data
61
+ if os.path.exists(target_path):
62
+ os.remove(target_path)
63
+ return True
64
+ except:
65
+ pass
66
+
67
+ with open(target_path, "wb") as f:
68
+ f.write(data)
69
+ else:
70
+ if not os.path.exists(content):
71
+ raise RuntimeError(f"License file not found: {content}")
72
+ shutil.copy(content, target_path)
73
+
74
+ os.environ['QLIC'] = target_dir
75
+ return True
76
+
77
+
78
+ def connect(license_path=None):
79
+ """
80
+ Establishes connection to kdb+.
81
+
82
+ Parameters
83
+ ----------
84
+ license_path : str, optional
85
+ Path to the license file or directory.
86
+ """
87
+ if license_path:
88
+ if not os.path.exists(license_path):
89
+ raise RuntimeError(f"License path does not exist: {license_path}")
90
+ os.environ['QLIC'] = license_path
91
+
92
+ try:
93
+ kx.q('1+1')
94
+ return True
95
+ except:
96
+ pass
97
+
98
+ token = (os.environ.get('KDB_TOKEN') or os.environ.get('KX_TOKEN', '')).strip()
99
+ if token:
100
+ install_license(token)
101
+ try:
102
+ kx.q('1+1')
103
+ return True
104
+ except:
105
+ pass
106
+
107
+ possible_paths = [
108
+ _get_project_lic_dir(),
109
+ os.path.expanduser("~/.qutepandas"),
110
+ '/Applications/kdb+:q',
111
+ os.path.expanduser("~/kdb+"),
112
+ os.path.expanduser("~/q"),
113
+ os.path.expanduser("~/.pykx")
114
+ ]
115
+
116
+ for path in possible_paths:
117
+ if not os.path.exists(path):
118
+ continue
119
+
120
+ if os.path.exists(os.path.join(path, "kc.lic")) or os.path.exists(os.path.join(path, "k4.lic")):
121
+ os.environ['QLIC'] = path
122
+ try:
123
+ kx.q('1+1')
124
+ return True
125
+ except:
126
+ continue
127
+
128
+ raise RuntimeError(
129
+ "No valid kdb+ license found. "
130
+ "Set KDB_TOKEN in .env or place kc.lic in local kdb_lic/ or ~/.qutepandas/"
131
+ )
132
+
133
+
134
+ def get_license_info():
135
+ """
136
+ Returns current license configuration status.
137
+ """
138
+ try:
139
+ kx.q('1+1')
140
+ return {
141
+ 'qlic_path': os.environ.get('QLIC', 'Not set'),
142
+ 'qhome_path': os.environ.get('QHOME', 'Not set'),
143
+ 'kx_token_set': 'Yes' if os.environ.get('KX_TOKEN') else 'No',
144
+ 'connection_status': 'Connected'
145
+ }
146
+ except Exception as e:
147
+ return {
148
+ 'qlic_path': os.environ.get('QLIC', 'Not set'),
149
+ 'qhome_path': os.environ.get('QHOME', 'Not set'),
150
+ 'kx_token_set': 'Yes' if os.environ.get('KX_TOKEN') else 'No',
151
+ 'connection_status': 'Failed',
152
+ 'error': str(e)
153
+ }
@@ -0,0 +1,121 @@
1
+ import pykx as kx
2
+ import pandas as pd
3
+ import os
4
+ import atexit
5
+ from ..utils import _handle_return
6
+
7
+ os.environ['PYKX_ENFORCE_EMBEDDED_IMPORT'] = '0'
8
+
9
+ def DataFrame(data, columns=None):
10
+ """
11
+ Creates a qutePandas DataFrame (internal pykx Table).
12
+
13
+ Parameters
14
+ ----------
15
+ data : array-like, dict, or pandas.DataFrame
16
+ Data to be stored in the table.
17
+ columns : list, optional
18
+ Column names to use if data does not already have them.
19
+
20
+ Returns
21
+ -------
22
+ pykx.Table
23
+ The resulting kdb+ table.
24
+ """
25
+ try:
26
+ if isinstance(data, pd.DataFrame):
27
+ q_res = kx.toq(data)
28
+ elif isinstance(data, (kx.Table, kx.KeyedTable)):
29
+ q_res = data
30
+ elif isinstance(data, dict):
31
+ q_res = _dict_to_table(data)
32
+ elif isinstance(data, list) and data and isinstance(data[0], list):
33
+ q_res = _lists_to_table(data, columns)
34
+ else:
35
+ q_res = _data_to_table(data, columns)
36
+ return _handle_return(q_res)
37
+ except Exception as e:
38
+ raise RuntimeError(f"Failed to create kdb+ table: {e}")
39
+
40
+
41
+ def _dict_to_table(data_dict):
42
+ """
43
+ Converts a dictionary to a kdb+ table.
44
+
45
+ Parameters
46
+ ----------
47
+ data_dict : dict
48
+ Dictionary to convert.
49
+
50
+ Returns
51
+ -------
52
+ pykx.Table
53
+ The resulting kdb+ table.
54
+ """
55
+ try:
56
+ q_dict = kx.toq(data_dict)
57
+ return kx.q("{flip x}", q_dict)
58
+ except Exception as e:
59
+ raise RuntimeError(f"Failed to create kdb+ table from dict: {e}")
60
+
61
+
62
+ def _lists_to_table(data_lists, columns=None):
63
+ """
64
+ Converts a list of lists to a kdb+ table.
65
+
66
+ Parameters
67
+ ----------
68
+ data_lists : list of lists
69
+ Data to convert.
70
+ columns : list, optional
71
+ Column names.
72
+
73
+ Returns
74
+ -------
75
+ pykx.Table
76
+ The resulting kdb+ table.
77
+ """
78
+ try:
79
+ if not data_lists:
80
+ return kx.q("([] )")
81
+
82
+ if columns is None:
83
+ columns = [f'col_{i}' for i in range(len(data_lists[0]))]
84
+
85
+ transposed = list(zip(*data_lists))
86
+ data_dict = {columns[i]: list(col_data) for i, col_data in enumerate(transposed)}
87
+ q_dict = kx.toq(data_dict)
88
+ return kx.q("{flip x}", q_dict)
89
+ except Exception as e:
90
+ raise RuntimeError(f"Failed to create kdb+ table from lists: {e}")
91
+
92
+
93
+ def _data_to_table(data, columns=None):
94
+ """
95
+ Converts generic data to a kdb+ table.
96
+
97
+ Parameters
98
+ ----------
99
+ data : any
100
+ Data to convert.
101
+ columns : list, optional
102
+ Column names.
103
+
104
+ Returns
105
+ -------
106
+ pykx.Table
107
+ The resulting kdb+ table.
108
+ """
109
+ try:
110
+ if hasattr(data, '__iter__') and not isinstance(data, (str, dict)):
111
+ data_list = list(data)
112
+ if columns:
113
+ data_dict = {col: [row[i] if hasattr(row, '__getitem__') else row for row in data_list]
114
+ for i, col in enumerate(columns)}
115
+ return _dict_to_table(data_dict)
116
+ else:
117
+ return kx.toq(pd.DataFrame({'data': data_list}))
118
+ else:
119
+ return kx.toq(pd.DataFrame({'data': [data]}))
120
+ except Exception as e:
121
+ raise RuntimeError(f"Failed to create kdb+ table from data: {e}")