qutePandas 1.1.0__tar.gz → 1.1.2__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.
- {qutepandas-1.1.0/qutePandas.egg-info → qutepandas-1.1.2}/PKG-INFO +1 -1
- {qutepandas-1.1.0 → qutepandas-1.1.2}/pyproject.toml +1 -1
- {qutepandas-1.1.0 → qutepandas-1.1.2}/qutePandas/__init__.py +41 -11
- {qutepandas-1.1.0 → qutepandas-1.1.2}/qutePandas/apply/apply.py +7 -1
- qutepandas-1.1.2/qutePandas/cleaning/fillna.py +49 -0
- {qutepandas-1.1.0 → qutepandas-1.1.2}/qutePandas/core/dataframe.py +0 -3
- {qutepandas-1.1.0 → qutepandas-1.1.2}/qutePandas/indexing/iloc.py +31 -10
- {qutepandas-1.1.0 → qutepandas-1.1.2/qutePandas.egg-info}/PKG-INFO +1 -1
- {qutepandas-1.1.0 → qutepandas-1.1.2}/qutePandas.egg-info/SOURCES.txt +1 -0
- {qutepandas-1.1.0 → qutepandas-1.1.2}/setup.py +1 -1
- qutepandas-1.1.2/tests/test_pykx_setup.py +274 -0
- qutepandas-1.1.0/qutePandas/cleaning/fillna.py +0 -36
- {qutepandas-1.1.0 → qutepandas-1.1.2}/MANIFEST.in +0 -0
- {qutepandas-1.1.0 → qutepandas-1.1.2}/README.md +0 -0
- {qutepandas-1.1.0 → qutepandas-1.1.2}/qutePandas/apply/__init__.py +0 -0
- {qutepandas-1.1.0 → qutepandas-1.1.2}/qutePandas/apply/apply_col.py +0 -0
- {qutepandas-1.1.0 → qutepandas-1.1.2}/qutePandas/cleaning/__init__.py +0 -0
- {qutepandas-1.1.0 → qutepandas-1.1.2}/qutePandas/cleaning/dropna.py +0 -0
- {qutepandas-1.1.0 → qutepandas-1.1.2}/qutePandas/cleaning/dropna_col.py +0 -0
- {qutepandas-1.1.0 → qutepandas-1.1.2}/qutePandas/cleaning/remove_duplicates.py +0 -0
- {qutepandas-1.1.0 → qutepandas-1.1.2}/qutePandas/core/__init__.py +0 -0
- {qutepandas-1.1.0 → qutepandas-1.1.2}/qutePandas/core/connection.py +0 -0
- {qutepandas-1.1.0 → qutepandas-1.1.2}/qutePandas/core/display.py +0 -0
- {qutepandas-1.1.0 → qutepandas-1.1.2}/qutePandas/grouping/__init__.py +0 -0
- {qutepandas-1.1.0 → qutepandas-1.1.2}/qutePandas/grouping/groupby_avg.py +0 -0
- {qutepandas-1.1.0 → qutepandas-1.1.2}/qutePandas/grouping/groupby_sum.py +0 -0
- {qutepandas-1.1.0 → qutepandas-1.1.2}/qutePandas/indexing/__init__.py +0 -0
- {qutepandas-1.1.0 → qutepandas-1.1.2}/qutePandas/indexing/loc.py +0 -0
- {qutepandas-1.1.0 → qutepandas-1.1.2}/qutePandas/introspection/__init__.py +0 -0
- {qutepandas-1.1.0 → qutepandas-1.1.2}/qutePandas/introspection/dtypes.py +0 -0
- {qutepandas-1.1.0 → qutepandas-1.1.2}/qutePandas/io/__init__.py +0 -0
- {qutepandas-1.1.0 → qutepandas-1.1.2}/qutePandas/io/from_csv.py +0 -0
- {qutepandas-1.1.0 → qutepandas-1.1.2}/qutePandas/io/to_csv.py +0 -0
- {qutepandas-1.1.0 → qutepandas-1.1.2}/qutePandas/joining/__init__.py +0 -0
- {qutepandas-1.1.0 → qutepandas-1.1.2}/qutePandas/joining/merge.py +0 -0
- {qutepandas-1.1.0 → qutepandas-1.1.2}/qutePandas/transformation/__init__.py +0 -0
- {qutepandas-1.1.0 → qutepandas-1.1.2}/qutePandas/transformation/cast.py +0 -0
- {qutepandas-1.1.0 → qutepandas-1.1.2}/qutePandas/transformation/drop_col.py +0 -0
- {qutepandas-1.1.0 → qutepandas-1.1.2}/qutePandas/transformation/rename.py +0 -0
- {qutepandas-1.1.0 → qutepandas-1.1.2}/qutePandas/utils.py +0 -0
- {qutepandas-1.1.0 → qutepandas-1.1.2}/qutePandas.egg-info/dependency_links.txt +0 -0
- {qutepandas-1.1.0 → qutepandas-1.1.2}/qutePandas.egg-info/requires.txt +0 -0
- {qutepandas-1.1.0 → qutepandas-1.1.2}/qutePandas.egg-info/top_level.txt +0 -0
- {qutepandas-1.1.0 → qutepandas-1.1.2}/requirements.txt +0 -0
- {qutepandas-1.1.0 → qutepandas-1.1.2}/setup.cfg +0 -0
- {qutepandas-1.1.0 → qutepandas-1.1.2}/tests/test_utils.py +0 -0
|
@@ -1,11 +1,11 @@
|
|
|
1
|
-
"""
|
|
2
|
-
qutePandas - A pandas-like library for q/kdb+
|
|
3
|
-
"""
|
|
4
|
-
|
|
5
1
|
import os
|
|
2
|
+
import sys
|
|
6
3
|
|
|
7
|
-
def
|
|
8
|
-
"""
|
|
4
|
+
def _setup_pykx_environment():
|
|
5
|
+
"""
|
|
6
|
+
Sets up the environment for PyKX BEFORE any PyKX imports.
|
|
7
|
+
This must run before importing any module that uses PyKX.
|
|
8
|
+
"""
|
|
9
9
|
root = os.path.abspath(os.path.join(os.path.dirname(__file__), ".."))
|
|
10
10
|
env_path = os.path.join(root, ".env")
|
|
11
11
|
|
|
@@ -20,17 +20,47 @@ def _setup_environment():
|
|
|
20
20
|
if len(parts) == 2:
|
|
21
21
|
key, value = parts
|
|
22
22
|
value = value.strip().strip('"').strip("'")
|
|
23
|
+
# Only set if not already in environment
|
|
23
24
|
if key.strip() and key.strip() not in os.environ:
|
|
24
25
|
os.environ[key.strip()] = value
|
|
25
|
-
|
|
26
|
+
|
|
27
|
+
# Check for valid license files in priority order
|
|
26
28
|
qutepandas_home = os.path.expanduser("~/.qutepandas")
|
|
27
29
|
local_kdb = os.path.join(root, "kdb_lic")
|
|
30
|
+
|
|
31
|
+
license_found = False
|
|
32
|
+
valid_license_path = None
|
|
33
|
+
|
|
28
34
|
if os.path.exists(os.path.join(local_kdb, "kc.lic")):
|
|
29
|
-
|
|
35
|
+
valid_license_path = local_kdb
|
|
36
|
+
license_found = True
|
|
30
37
|
elif os.path.exists(os.path.join(qutepandas_home, "kc.lic")):
|
|
31
|
-
|
|
38
|
+
valid_license_path = qutepandas_home
|
|
39
|
+
license_found = True
|
|
40
|
+
|
|
41
|
+
if license_found and valid_license_path:
|
|
42
|
+
os.environ['QLIC'] = valid_license_path
|
|
43
|
+
os.environ.pop('PYKX_UNLICENSED', None)
|
|
44
|
+
elif 'QLIC' in os.environ:
|
|
45
|
+
qlic_path = os.environ['QLIC']
|
|
46
|
+
if not (os.path.exists(os.path.join(qlic_path, "kc.lic")) or
|
|
47
|
+
os.path.exists(os.path.join(qlic_path, "k4.lic"))):
|
|
48
|
+
del os.environ['QLIC']
|
|
49
|
+
if 'QHOME' in os.environ:
|
|
50
|
+
del os.environ['QHOME']
|
|
51
|
+
license_found = False
|
|
52
|
+
|
|
53
|
+
if not license_found and 'PYKX_UNLICENSED' not in os.environ:
|
|
54
|
+
os.environ['PYKX_UNLICENSED'] = 'true'
|
|
55
|
+
|
|
56
|
+
if 'PYKX_RELEASE_GIL' not in os.environ:
|
|
57
|
+
os.environ['PYKX_RELEASE_GIL'] = 'true'
|
|
58
|
+
|
|
59
|
+
if 'PYKX_ENFORCE_EMBEDDED_IMPORT' not in os.environ:
|
|
60
|
+
os.environ['PYKX_ENFORCE_EMBEDDED_IMPORT'] = '0'
|
|
61
|
+
|
|
62
|
+
_setup_pykx_environment()
|
|
32
63
|
|
|
33
|
-
_setup_environment()
|
|
34
64
|
from .core.dataframe import DataFrame
|
|
35
65
|
from .core.connection import connect, get_license_info, install_license
|
|
36
66
|
from .core.display import py, np, pd, pa, pt, print
|
|
@@ -59,4 +89,4 @@ from .introspection.dtypes import dtypes
|
|
|
59
89
|
|
|
60
90
|
from .indexing import loc, iloc
|
|
61
91
|
|
|
62
|
-
__version__ = "1.
|
|
92
|
+
__version__ = "1.1.2"
|
|
@@ -2,6 +2,10 @@ import pykx as kx
|
|
|
2
2
|
import pandas as pd
|
|
3
3
|
from ..utils import _ensure_q_table, _handle_return
|
|
4
4
|
|
|
5
|
+
_VECTORIZED_AXIS1 = {
|
|
6
|
+
'sum': '{sum (0^) each value flip x}',
|
|
7
|
+
}
|
|
8
|
+
|
|
5
9
|
|
|
6
10
|
def apply(df, func, axis=0, return_type='q'):
|
|
7
11
|
"""
|
|
@@ -39,7 +43,9 @@ def apply(df, func, axis=0, return_type='q'):
|
|
|
39
43
|
return pd.Series(ret) if return_type == 'p' else ret
|
|
40
44
|
|
|
41
45
|
if isinstance(func, str):
|
|
42
|
-
if axis == 1:
|
|
46
|
+
if axis == 1 and func in _VECTORIZED_AXIS1:
|
|
47
|
+
result = kx.q(_VECTORIZED_AXIS1[func], q_table)
|
|
48
|
+
elif axis == 1:
|
|
43
49
|
result = kx.q(f"{{({func}) each x}}", q_table)
|
|
44
50
|
else:
|
|
45
51
|
result = kx.q(f"{{({func}) each flip x}}", q_table)
|
|
@@ -0,0 +1,49 @@
|
|
|
1
|
+
import pykx as kx
|
|
2
|
+
import pandas as pd
|
|
3
|
+
from ..utils import _ensure_q_table, _handle_return
|
|
4
|
+
|
|
5
|
+
def fillna(df, col_or_values, fill_value=None, return_type='q'):
|
|
6
|
+
"""
|
|
7
|
+
Fills null values in specified columns.
|
|
8
|
+
|
|
9
|
+
Can be called in two ways:
|
|
10
|
+
fillna(df, values_dict, return_type='q')
|
|
11
|
+
fillna(df, col_name, fill_value, return_type='q')
|
|
12
|
+
|
|
13
|
+
Parameters
|
|
14
|
+
----------
|
|
15
|
+
df : pandas.DataFrame or pykx.Table
|
|
16
|
+
Input DataFrame.
|
|
17
|
+
col_or_values : str or dict
|
|
18
|
+
If str, the column name to fill (requires fill_value).
|
|
19
|
+
If dict, a mapping of column names to fill values.
|
|
20
|
+
fill_value : scalar, optional
|
|
21
|
+
The value to fill nulls with when col_or_values is a column name.
|
|
22
|
+
return_type : str, default 'q'
|
|
23
|
+
Desired return type ('p' or 'q').
|
|
24
|
+
|
|
25
|
+
Returns
|
|
26
|
+
-------
|
|
27
|
+
pandas.DataFrame or pykx.Table
|
|
28
|
+
DataFrame with nulls filled.
|
|
29
|
+
"""
|
|
30
|
+
try:
|
|
31
|
+
if isinstance(col_or_values, str):
|
|
32
|
+
if fill_value is None:
|
|
33
|
+
raise ValueError("fill_value is required when col_or_values is a column name")
|
|
34
|
+
values = {col_or_values: fill_value}
|
|
35
|
+
elif isinstance(col_or_values, dict):
|
|
36
|
+
values = col_or_values
|
|
37
|
+
else:
|
|
38
|
+
raise ValueError("col_or_values must be a column name (str) or a dictionary")
|
|
39
|
+
|
|
40
|
+
q_table = _ensure_q_table(df)
|
|
41
|
+
result = q_table
|
|
42
|
+
|
|
43
|
+
for col, val in values.items():
|
|
44
|
+
fill_val = f'`{val}' if isinstance(val, str) else str(val)
|
|
45
|
+
result = kx.q(f"{{update {col}:{fill_val}^{col} from x}}", result)
|
|
46
|
+
|
|
47
|
+
return _handle_return(result, return_type)
|
|
48
|
+
except Exception as e:
|
|
49
|
+
raise RuntimeError(f"Failed to fillna: {e}")
|
|
@@ -1,11 +1,8 @@
|
|
|
1
1
|
import pykx as kx
|
|
2
2
|
import pandas as pd
|
|
3
|
-
import os
|
|
4
3
|
import atexit
|
|
5
4
|
from ..utils import _handle_return
|
|
6
5
|
|
|
7
|
-
os.environ['PYKX_ENFORCE_EMBEDDED_IMPORT'] = '0'
|
|
8
|
-
|
|
9
6
|
def DataFrame(data, columns=None):
|
|
10
7
|
"""
|
|
11
8
|
Creates a qutePandas DataFrame (internal pykx Table).
|
|
@@ -22,10 +22,15 @@ def iloc(df, rows=None, cols=None, return_type='q'):
|
|
|
22
22
|
Subset of the inputs.
|
|
23
23
|
"""
|
|
24
24
|
table = _ensure_q_table(df)
|
|
25
|
-
|
|
25
|
+
|
|
26
26
|
count = kx.q("count", table).py()
|
|
27
27
|
all_cols = kx.q("cols", table).py()
|
|
28
|
-
|
|
28
|
+
|
|
29
|
+
# Track whether we can use the fast sublist path for contiguous row slices
|
|
30
|
+
_use_sublist = False
|
|
31
|
+
_slice_start = 0
|
|
32
|
+
_slice_count = 0
|
|
33
|
+
|
|
29
34
|
if rows is None:
|
|
30
35
|
row_indices = None
|
|
31
36
|
elif isinstance(rows, int):
|
|
@@ -33,7 +38,14 @@ def iloc(df, rows=None, cols=None, return_type='q'):
|
|
|
33
38
|
if q_rows < 0: q_rows += count
|
|
34
39
|
row_indices = [q_rows]
|
|
35
40
|
elif isinstance(rows, slice):
|
|
36
|
-
|
|
41
|
+
start, stop, step = rows.indices(count)
|
|
42
|
+
if step == 1:
|
|
43
|
+
_use_sublist = True
|
|
44
|
+
_slice_start = start
|
|
45
|
+
_slice_count = stop - start
|
|
46
|
+
row_indices = None # Not needed — sublist handles it
|
|
47
|
+
else:
|
|
48
|
+
row_indices = list(range(start, stop, step))
|
|
37
49
|
else:
|
|
38
50
|
row_indices = list(rows)
|
|
39
51
|
row_indices = [r + count if r < 0 else r for r in row_indices]
|
|
@@ -47,27 +59,36 @@ def iloc(df, rows=None, cols=None, return_type='q'):
|
|
|
47
59
|
target_cols = [all_cols[i] for i in range(*cols.indices(len(all_cols)))]
|
|
48
60
|
else:
|
|
49
61
|
target_cols = [all_cols[i] for i in cols]
|
|
50
|
-
|
|
51
|
-
if row_indices is None and target_cols is None:
|
|
62
|
+
|
|
63
|
+
if not _use_sublist and row_indices is None and target_cols is None:
|
|
52
64
|
return _handle_return(table, return_type)
|
|
53
65
|
|
|
54
|
-
|
|
66
|
+
# Fast path: contiguous row slice via q's sublist (no Python list overhead)
|
|
67
|
+
if _use_sublist:
|
|
68
|
+
sublist_args = kx.LongVector([_slice_start, _slice_count])
|
|
69
|
+
if target_cols is not None:
|
|
70
|
+
syms = kx.SymbolVector(target_cols)
|
|
71
|
+
cols_dict = kx.q('!', syms, syms)
|
|
72
|
+
q_res = kx.q('{?[sublist[y;x];();0b;z]}', table, sublist_args, cols_dict)
|
|
73
|
+
else:
|
|
74
|
+
q_res = kx.q('{sublist[y;x]}', table, sublist_args)
|
|
75
|
+
elif row_indices is not None and target_cols is not None:
|
|
55
76
|
q_query = '{?[x y;();0b;z]}'
|
|
56
77
|
syms = kx.SymbolVector(target_cols)
|
|
57
78
|
cols_dict = kx.q('!', syms, syms)
|
|
58
79
|
q_res = kx.q(q_query, table, kx.LongVector(row_indices), cols_dict)
|
|
59
|
-
|
|
80
|
+
|
|
60
81
|
elif row_indices is not None:
|
|
61
82
|
q_query = '{x y}'
|
|
62
83
|
q_res = kx.q(q_query, table, kx.LongVector(row_indices))
|
|
63
|
-
|
|
84
|
+
|
|
64
85
|
elif target_cols is not None:
|
|
65
86
|
q_query = '{?[x;();0b;y]}'
|
|
66
87
|
syms = kx.SymbolVector(target_cols)
|
|
67
88
|
cols_dict = kx.q('!', syms, syms)
|
|
68
89
|
q_res = kx.q(q_query, table, cols_dict)
|
|
69
|
-
|
|
90
|
+
|
|
70
91
|
else:
|
|
71
92
|
q_res = table
|
|
72
93
|
|
|
73
|
-
return _handle_return(q_res, return_type)
|
|
94
|
+
return _handle_return(q_res, return_type)
|
|
@@ -0,0 +1,274 @@
|
|
|
1
|
+
#!/usr/bin/env python3
|
|
2
|
+
"""
|
|
3
|
+
PyKX + qutePandas Setup Verification Test
|
|
4
|
+
|
|
5
|
+
This standalone test file verifies that:
|
|
6
|
+
1. PyKX license is properly configured
|
|
7
|
+
2. Embedded q initializes successfully
|
|
8
|
+
3. qutePandas imports without errors
|
|
9
|
+
4. Basic DataFrame operations work correctly
|
|
10
|
+
|
|
11
|
+
Run this file to diagnose and verify your PyKX + qutePandas setup.
|
|
12
|
+
"""
|
|
13
|
+
|
|
14
|
+
import sys
|
|
15
|
+
import os
|
|
16
|
+
from pathlib import Path
|
|
17
|
+
|
|
18
|
+
|
|
19
|
+
def print_section(title):
|
|
20
|
+
"""Print a formatted section header."""
|
|
21
|
+
print(f"\n{'=' * 70}")
|
|
22
|
+
print(f" {title}")
|
|
23
|
+
print('=' * 70)
|
|
24
|
+
|
|
25
|
+
|
|
26
|
+
def print_result(test_name, passed, message=""):
|
|
27
|
+
"""Print a test result with status indicator."""
|
|
28
|
+
status = "✅ PASS" if passed else "❌ FAIL"
|
|
29
|
+
print(f"{status}: {test_name}")
|
|
30
|
+
if message:
|
|
31
|
+
print(f" {message}")
|
|
32
|
+
|
|
33
|
+
|
|
34
|
+
def check_python_version():
|
|
35
|
+
"""Verify Python version is 3.8 or higher."""
|
|
36
|
+
print_section("Python Version Check")
|
|
37
|
+
version = sys.version_info
|
|
38
|
+
print(f"Python version: {version.major}.{version.minor}.{version.micro}")
|
|
39
|
+
|
|
40
|
+
if version.major >= 3 and version.minor >= 8:
|
|
41
|
+
print_result("Python Version", True, "Python 3.8+ detected")
|
|
42
|
+
return True
|
|
43
|
+
else:
|
|
44
|
+
print_result("Python Version", False, "Python 3.8+ required")
|
|
45
|
+
return False
|
|
46
|
+
|
|
47
|
+
|
|
48
|
+
def check_license_files():
|
|
49
|
+
"""Check for kdb+ license files in common locations."""
|
|
50
|
+
print_section("License File Detection")
|
|
51
|
+
|
|
52
|
+
locations = [
|
|
53
|
+
Path.home() / ".qutepandas" / "kc.lic",
|
|
54
|
+
Path(__file__).parent.parent / "kdb_lic" / "kc.lic",
|
|
55
|
+
Path.home() / "kdb+" / "kc.lic",
|
|
56
|
+
Path.home() / "q" / "kc.lic",
|
|
57
|
+
]
|
|
58
|
+
|
|
59
|
+
found_licenses = []
|
|
60
|
+
for loc in locations:
|
|
61
|
+
if loc.exists():
|
|
62
|
+
found_licenses.append(str(loc))
|
|
63
|
+
print(f" Found: {loc}")
|
|
64
|
+
|
|
65
|
+
qlic = os.environ.get('QLIC')
|
|
66
|
+
kx_token = os.environ.get('KX_TOKEN') or os.environ.get('KDB_TOKEN')
|
|
67
|
+
pykx_unlicensed = os.environ.get('PYKX_UNLICENSED')
|
|
68
|
+
|
|
69
|
+
print(f"\nEnvironment Variables:")
|
|
70
|
+
print(f" QLIC: {qlic if qlic else 'Not set'}")
|
|
71
|
+
print(f" KX_TOKEN: {'Set' if kx_token else 'Not set'}")
|
|
72
|
+
print(f" PYKX_UNLICENSED: {pykx_unlicensed if pykx_unlicensed else 'Not set'}")
|
|
73
|
+
|
|
74
|
+
if found_licenses or kx_token or pykx_unlicensed == 'true':
|
|
75
|
+
print_result("License Detection", True,
|
|
76
|
+
f"Found {len(found_licenses)} license file(s) or valid configuration")
|
|
77
|
+
return True
|
|
78
|
+
else:
|
|
79
|
+
print_result("License Detection", False,
|
|
80
|
+
"No license found - will attempt unlicensed mode")
|
|
81
|
+
return False
|
|
82
|
+
|
|
83
|
+
|
|
84
|
+
def test_pykx_import():
|
|
85
|
+
"""Test PyKX import and initialization."""
|
|
86
|
+
print_section("PyKX Import and Initialization")
|
|
87
|
+
|
|
88
|
+
try:
|
|
89
|
+
import pykx as kx
|
|
90
|
+
print(f"PyKX version: {kx.__version__}")
|
|
91
|
+
print_result("PyKX Import", True)
|
|
92
|
+
return True, kx
|
|
93
|
+
except Exception as e:
|
|
94
|
+
print_result("PyKX Import", False, str(e))
|
|
95
|
+
return False, None
|
|
96
|
+
|
|
97
|
+
|
|
98
|
+
def test_embedded_q(kx):
|
|
99
|
+
"""Test embedded q functionality."""
|
|
100
|
+
print_section("Embedded Q Initialization")
|
|
101
|
+
|
|
102
|
+
if kx is None:
|
|
103
|
+
print_result("Embedded Q", False, "PyKX not available")
|
|
104
|
+
return False
|
|
105
|
+
|
|
106
|
+
try:
|
|
107
|
+
# Test basic q expression
|
|
108
|
+
result = kx.q('1+1')
|
|
109
|
+
expected = 2
|
|
110
|
+
|
|
111
|
+
if result.py() == expected:
|
|
112
|
+
print_result("Basic Q Expression", True, f"1+1 = {result.py()}")
|
|
113
|
+
else:
|
|
114
|
+
print_result("Basic Q Expression", False,
|
|
115
|
+
f"Expected {expected}, got {result.py()}")
|
|
116
|
+
return False
|
|
117
|
+
|
|
118
|
+
# Test table creation
|
|
119
|
+
table = kx.q('([] a:1 2 3; b:`x`y`z)')
|
|
120
|
+
print(f"Created q table:\n{table}")
|
|
121
|
+
print_result("Q Table Creation", True)
|
|
122
|
+
|
|
123
|
+
return True
|
|
124
|
+
except Exception as e:
|
|
125
|
+
print_result("Embedded Q", False, str(e))
|
|
126
|
+
return False
|
|
127
|
+
|
|
128
|
+
|
|
129
|
+
def test_qutepandas_import():
|
|
130
|
+
"""Test qutePandas import."""
|
|
131
|
+
print_section("qutePandas Import")
|
|
132
|
+
|
|
133
|
+
try:
|
|
134
|
+
import qutePandas as qpd
|
|
135
|
+
print(f"qutePandas version: {qpd.__version__}")
|
|
136
|
+
print_result("qutePandas Import", True)
|
|
137
|
+
return True, qpd
|
|
138
|
+
except Exception as e:
|
|
139
|
+
print_result("qutePandas Import", False, str(e))
|
|
140
|
+
print(f"\nFull error:\n{e}")
|
|
141
|
+
import traceback
|
|
142
|
+
traceback.print_exc()
|
|
143
|
+
return False, None
|
|
144
|
+
|
|
145
|
+
|
|
146
|
+
def test_qutepandas_operations(qpd):
|
|
147
|
+
"""Test basic qutePandas DataFrame operations."""
|
|
148
|
+
print_section("qutePandas DataFrame Operations")
|
|
149
|
+
|
|
150
|
+
if qpd is None:
|
|
151
|
+
print_result("DataFrame Operations", False, "qutePandas not available")
|
|
152
|
+
return False
|
|
153
|
+
|
|
154
|
+
try:
|
|
155
|
+
# Test 1: Create DataFrame from dict
|
|
156
|
+
df = qpd.DataFrame({
|
|
157
|
+
'id': [1, 2, 3, 4, 5],
|
|
158
|
+
'name': ['Alice', 'Bob', 'Charlie', 'David', 'Eve'],
|
|
159
|
+
'score': [95.5, 87.3, 92.1, 78.9, 88.4]
|
|
160
|
+
})
|
|
161
|
+
print(f"Created DataFrame:\n{df}")
|
|
162
|
+
print_result("DataFrame Creation", True)
|
|
163
|
+
|
|
164
|
+
# Test 2: Get dtypes
|
|
165
|
+
dtypes = qpd.dtypes(df)
|
|
166
|
+
print(f"\nDataFrame dtypes:\n{dtypes}")
|
|
167
|
+
print_result("Get Dtypes", True)
|
|
168
|
+
|
|
169
|
+
# Test 3: Convert to pandas
|
|
170
|
+
pdf = df.pd()
|
|
171
|
+
print(f"\nConverted to pandas:\n{pdf}")
|
|
172
|
+
print_result("Convert to Pandas", True, f"Shape: {pdf.shape}")
|
|
173
|
+
|
|
174
|
+
# Test 4: Filter operation
|
|
175
|
+
filtered = qpd.apply(df, lambda x: x['score'] > 85)
|
|
176
|
+
print(f"\nFiltered (score > 85):\n{filtered}")
|
|
177
|
+
print_result("Filter Operation", True)
|
|
178
|
+
|
|
179
|
+
# Test 5: Groupby operation
|
|
180
|
+
df2 = qpd.DataFrame({
|
|
181
|
+
'category': ['A', 'B', 'A', 'B', 'A'],
|
|
182
|
+
'value': [10, 20, 30, 40, 50]
|
|
183
|
+
})
|
|
184
|
+
grouped = qpd.groupby_sum(df2, 'category', 'value')
|
|
185
|
+
print(f"\nGroupby sum:\n{grouped}")
|
|
186
|
+
print_result("Groupby Operation", True)
|
|
187
|
+
|
|
188
|
+
return True
|
|
189
|
+
except Exception as e:
|
|
190
|
+
print_result("DataFrame Operations", False, str(e))
|
|
191
|
+
import traceback
|
|
192
|
+
traceback.print_exc()
|
|
193
|
+
return False
|
|
194
|
+
|
|
195
|
+
|
|
196
|
+
def print_diagnostic_info():
|
|
197
|
+
"""Print diagnostic information for troubleshooting."""
|
|
198
|
+
print_section("Diagnostic Information")
|
|
199
|
+
|
|
200
|
+
import platform
|
|
201
|
+
print(f"OS: {platform.system()} {platform.release()}")
|
|
202
|
+
print(f"Architecture: {platform.machine()}")
|
|
203
|
+
print(f"Python: {sys.version}")
|
|
204
|
+
|
|
205
|
+
print(f"\nEnvironment Variables:")
|
|
206
|
+
for var in ['QHOME', 'QLIC', 'PYKX_UNLICENSED', 'PYKX_RELEASE_GIL',
|
|
207
|
+
'PYKX_ENFORCE_EMBEDDED_IMPORT', 'KX_TOKEN', 'KDB_TOKEN']:
|
|
208
|
+
value = os.environ.get(var, 'Not set')
|
|
209
|
+
print(f" {var}: {value}")
|
|
210
|
+
|
|
211
|
+
print(f"\nPython Path:")
|
|
212
|
+
for path in sys.path[:5]:
|
|
213
|
+
print(f" {path}")
|
|
214
|
+
|
|
215
|
+
|
|
216
|
+
def main():
|
|
217
|
+
"""Run all tests."""
|
|
218
|
+
print("\n" + "=" * 70)
|
|
219
|
+
print(" PyKX + qutePandas Setup Verification")
|
|
220
|
+
print("=" * 70)
|
|
221
|
+
|
|
222
|
+
results = []
|
|
223
|
+
|
|
224
|
+
# Test 1: Python version
|
|
225
|
+
results.append(check_python_version())
|
|
226
|
+
|
|
227
|
+
# Test 2: License detection
|
|
228
|
+
results.append(check_license_files())
|
|
229
|
+
|
|
230
|
+
# Test 3: PyKX import
|
|
231
|
+
pykx_ok, kx = test_pykx_import()
|
|
232
|
+
results.append(pykx_ok)
|
|
233
|
+
|
|
234
|
+
# Test 4: Embedded q
|
|
235
|
+
if pykx_ok:
|
|
236
|
+
results.append(test_embedded_q(kx))
|
|
237
|
+
else:
|
|
238
|
+
results.append(False)
|
|
239
|
+
|
|
240
|
+
# Test 5: qutePandas import
|
|
241
|
+
qpd_ok, qpd = test_qutepandas_import()
|
|
242
|
+
results.append(qpd_ok)
|
|
243
|
+
|
|
244
|
+
# Test 6: qutePandas operations
|
|
245
|
+
if qpd_ok:
|
|
246
|
+
results.append(test_qutepandas_operations(qpd))
|
|
247
|
+
else:
|
|
248
|
+
results.append(False)
|
|
249
|
+
|
|
250
|
+
# Print diagnostic info
|
|
251
|
+
print_diagnostic_info()
|
|
252
|
+
|
|
253
|
+
# Summary
|
|
254
|
+
print_section("Test Summary")
|
|
255
|
+
passed = sum(results)
|
|
256
|
+
total = len(results)
|
|
257
|
+
|
|
258
|
+
print(f"\nTests Passed: {passed}/{total}")
|
|
259
|
+
|
|
260
|
+
if passed == total:
|
|
261
|
+
print("\n🎉 SUCCESS! PyKX + qutePandas is properly configured.")
|
|
262
|
+
return 0
|
|
263
|
+
else:
|
|
264
|
+
print("\n⚠️ ISSUES DETECTED. See failures above for details.")
|
|
265
|
+
print("\nCommon solutions:")
|
|
266
|
+
print(" 1. Ensure kc.lic is in ~/.qutepandas/ or project kdb_lic/")
|
|
267
|
+
print(" 2. Set PYKX_UNLICENSED=true for unlicensed mode")
|
|
268
|
+
print(" 3. Check that Python is 3.8+")
|
|
269
|
+
print(" 4. Reinstall: pip install --upgrade pykx")
|
|
270
|
+
return 1
|
|
271
|
+
|
|
272
|
+
|
|
273
|
+
if __name__ == "__main__":
|
|
274
|
+
sys.exit(main())
|
|
@@ -1,36 +0,0 @@
|
|
|
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}")
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|