OSCR

From API to Action: A Multi-Model Comparison of OpenAI, Anthropic, Google, and Meta LLMs for Clinical Trial Data Extraction.

Code ↔ Paper

14 matches between paragraphs of the paper and lines of its authors' code, computed by the harvester (lexical-v1). Click a colored paragraph or line to see its counterpart.

The 14 matches
  1. [1] § Appendix A. API Fields Retrieved from ClinicalTrials.gov ↔ notebooks/01_dataset_pull.ipynb, lines 191–265 · score 1.00 · BaselineCategoryTitle, OtherOutcomeDescription, BriefTitle, CompletionDate, ConditionMeshTerm, EligibilityCriteria
  2. [2] § Appendix B. Extraction Prompt and JSON Schema ↔ notebooks/03_LLM_processor.ipynb, lines 547–639 · score 0.95 · brain_stimulation_used, confidence_level, is_noninvasive, primary_type, primary_target, secondary_targets
  3. [3] § 2. Materials and Methods › 2.4. LLM Configuration and Architecture ↔ notebooks/03_LLM_processor.ipynb, lines 166–297 · score 0.79 · max token, meta llama, OpenRouter API, temperature, configured, OpenAI
  4. [4] § 2. Materials and Methods › 2.1. Data Source and Collection ↔ notebooks/01_dataset_pull.ipynb, lines 467–584 · score 0.74 · NCT IDs, eligibility criteria, batch, enrollment, location, status
  5. [5] § 2. Materials and Methods › 2.2. Search Strategy and Trial Selection ↔ figures/make_figure1_search_strategy.py, lines 10–29 · score 0.72 · Parkinson disease tdcs, 19–20, interface, July, variability, clinical
  6. [6] § 2. Materials and Methods › 2.6. Statistical Analysis ↔ notebooks/paper_stats_v2.ipynb, lines 250–341 · score 0.71 · Pearson correlation, Absolute Error, confusion matrices, milliamperes, MAE, numeric
  7. [7] § 2. Materials and Methods › 2.2. Search Strategy and Trial Selection ↔ figures/make_figure1_search_strategy.py, lines 10–29 · score 0.66 · Web interface variability, identical clinical queries, 19–20, Parkinson, July, tDCS
  8. [8] § 2. Materials and Methods › 2.2. Search Strategy and Trial Selection ↔ figures/make_figure1_search_strategy.py, lines 47–61 · score 0.64 · regex filter, tDCS, NCT02349789, NCT07010328, NCT03217110, NCT03221413
  9. [9] § 2. Materials and Methods › 2.6. Statistical Analysis ↔ notebooks/paper_stats_v2.ipynb, lines 106–176 · score 0.63 · motor cortex, tDCS, canonical, transcranial, brain stimulation, class
  10. [10] § 2. Materials and Methods › 2.5. Prompt Design and JSON Schema ↔ notebooks/03_LLM_processor.ipynb, lines 547–639 · score 0.62 · brain regions, relevant quotes, Medium, brain stimulation, noninvasive, confidence
  11. [11] § 3. Results › 3.6. Validation Against an Expert Gold Standard ↔ notebooks/paper_stats_v2.ipynb, lines 106–176 · score 0.61 · primary motor cortex, tDCS, canonical, M1, brain stimulation, synonym
  12. [12] § 3. Results › 3.1. Overall Agreement Patterns ↔ notebooks/paper_stats_table_2.ipynb, lines 176–291 · score 0.57 · intraclass correlation, pairwise comparisons, ICC, rater, intensity, duration
  13. [13] § 2. Materials and Methods › 2.5. Prompt Design and JSON Schema ↔ notebooks/03_LLM_processor.ipynb, lines 919–996 · score 0.56 · BriefSummary, DetailedDescription, brain stimulation, LLM, Prompt, field
  14. [14] § 2. Materials and Methods › 2.6. Statistical Analysis ↔ notebooks/paper_stats_table_2.ipynb, lines 176–291 · score 0.55 · intraclass correlation, ICC, raters, variables, Pairwise, score

Paper

Loaded from Europe PMC by your browser, not stored by OSCR: doi.org · Europe PMC

The paper is loaded when this pane is shown.

The authors' code

Jupyter notebook · 1,700 lines · 59 KB · MIT · 4 matches

  1. # %%
  2. import os
  3. import pandas as pd
  4. import numpy as np
  5. import openai
  6. import requests
  7. import pandas as pd
  8. import openai
  9. import json
  10. import logging
  11. import time
  12. from typing import List, Dict, Any, Optional
  13. from enum import Enum
  14. from dataclasses import dataclass
  15. import openrouter
  16. # %%
  17. # Import the openrouter module
  18. # Store API key in a separate file
  19. with open('../config/openrouter_api_key.txt', 'r') as f:
  20. api_key = f.read().strip()
  21. openrouter.api_key = api_key
  22. # %%
  23. # Store API key in a separate file
  24. with open('../config/api_key.txt', 'r') as f:
  25. api_key = f.read().strip()
  26. openai.api_key = api_key
  27. # %% [markdown]
  28. # %%
  29. # disease = 'Parkinson'
  30. # %%
  31. # Define file path
  32. file_path_parquet = os.path.join('../data/filtered', '02_simple_filter.parquet')
  33. # Load the DataFrame from the Parquet file
  34. filtered_df_covariates = pd.read_parquet(file_path_parquet)
  35. # Display the DataFrame
  36. print(filtered_df_covariates)
  37. # %%
  38. print(filtered_df_covariates.columns)
  39. # %%
  40. #test openai
  41. # response = openai.chat.completions.create(
  42. # model='gpt-3.5-turbo',
  43. # messages=[
  44. # {"role": "user", "content": "Hello, how are you?"}
  45. # ],
  46. # max_tokens=50,
  47. # temperature=0.5,
  48. # )
  49. # print(response.choices[0].message.content.strip())
  50. # %%
  51. # import requests
  52. # import json
  53. # # Configuration Variables
  54. # API_KEY = openrouter.api_key # Ensure this is defined
  55. # SITE_URL = 'https://pd-research.com' # Your site's URL
  56. # APP_NAME = 'PD Research' # Your application's name
  57. # def send_test_prompt():
  58. # print("Starting the API request...") # Debugging statement
  59. # try:
  60. # # Prepare the payload
  61. # payload = {
  62. # "model": "openai/o1-mini-2024-09-12", # Updated model
  63. # "messages": [
  64. # {
  65. # "role": "user",
  66. # "content": "What is the average age Parkinson's disease starts?"
  67. # }
  68. # ],
  69. # "max_tokens": 150, # Increased max_tokens
  70. # "temperature": 0.7, # Increased temperature for more varied responses
  71. # }
  72. # # Make the POST request to the OpenRouter API
  73. # response = requests.post(
  74. # url="https://openrouter.ai/api/v1/chat/completions",
  75. # headers={
  76. # "Authorization": f"Bearer {API_KEY}",
  77. # "HTTP-Referer": SITE_URL, # Optional: Include if you want your app featured
  78. # "X-Title": APP_NAME, # Optional: Shows your app name on OpenRouter rankings
  79. # },
  80. # data=json.dumps(payload)
  81. # )
  82. # print("API request sent. Awaiting response...") # Debugging statement
  83. # # Check if the request was successful
  84. # if response.status_code == 200:
  85. # print("Received successful response from the API.") # Debugging statement
  86. # response_data = response.json()
  87. # # Print the full JSON response for debugging
  88. # print("Full Response JSON:")
  89. # print(json.dumps(response_data, indent=4))
  90. # # Extract the assistant's reply
  91. # try:
  92. # assistant_reply = response_data['choices'][0]['message']['content'].strip()
  93. # print(f"Assistant's reply: {assistant_reply}")
  94. # except (KeyError, IndexError) as e:
  95. # print("Error extracting assistant's reply:", e)
  96. # print("Full response:", json.dumps(response_data, indent=4))
  97. # else:
  98. # print(f"Request failed with status code {response.status_code}")
  99. # print("Response:", response.text)
  100. # except requests.exceptions.RequestException as e:
  101. # print("An error occurred while making the request:", e)
  102. # # --- Execute the Test Prompt ---
  103. # send_test_prompt()
  104. # %%
  105. def generate_prompt(self, context: str) -> str:
  106. """Generate analysis prompt from context"""
  107. return f"""
  108. Analyze whether brain stimulation was used in this trial. If so, provide details.
  109. IMPORTANT: Respond ONLY with a JSON object in the EXACT format below. Do not include any additional text or explanations.
  110. {{
  111. "brain_stimulation_used": "Yes" or "No",
  112. "stimulation_details": {{
  113. "primary_type": "e.g., tDCS, TMS, tACS, DBS, etc." or null,
  114. "is_noninvasive": true or false,
  115. "primary_target": "Primary brain region or null",
  116. "secondary_targets": ["List of secondary regions"] or [],
  117. "stimulation_parameters": {{
  118. "intensity": "e.g., 2mA" or null,
  119. "duration": "e.g., 20 minutes" or null
  120. }}
  121. }},
  122. "confidence_level": "High", "Medium", or "Low",
  123. "relevant_quotes": ["Direct quotes supporting the analysis"]
  124. }}
  125. Context:
  126. {context}
  127. """
  128. # %%
  129. import openai
  130. import os
  131. import time
  132. from autogen import ConversableAgent, UserProxyAgent
  133. from autogen.agentchat.contrib.capabilities.teachability import Teachability
  134. ###############################################################################
  135. # 1) Hardcode your OpenRouter API key and base URL
  136. ###############################################################################
  137. openrouter_api_key = "sk-or-v1-REPLACE_ME_WITH_REAL_KEY"
  138. openai.api_key = openrouter_api_key
  139. openai.api_base = "https://openrouter.ai/api/v1"
  140. # Optionally set extra OpenRouter headers
  141. openai.request_headers = {
  142. "Authorization": f"Bearer {openrouter_api_key}",
  143. "X-Title": "MyMultiAgentApp",
  144. "HTTP-Referer": "https://example.com",
  145. }
  146. ###############################################################################
  147. # 2) Define multiple model names that are known to work with your key
  148. # (If you don't have GPT-4 access, remove "gpt-4".)
  149. ###############################################################################
  150. model_list = [
  151. "gpt-4",
  152. "anthropic/claude-3.5-sonnet",
  153. "meta-llama/llama-3-70b-instruct",
  154. ]
  155. ###############################################################################
  156. # 3) Build an agent for each model, with Teachability
  157. ###############################################################################
  158. def build_agent_for_model(model_name: str):
  159. """
  160. Creates a ConversableAgent configured for a specific model.
  161. Attaches Teachability with a local vector DB folder
  162. unique to the model_name.
  163. """
  164. config_list = [
  165. {
  166. "model": model_name,
  167. "temperature": 0.5, # or any you prefer
  168. "max_tokens": 4096, # might need a bigger limit for some models
  169. "top_p": 1.0,
  170. }
  171. ]
  172. agent = ConversableAgent(
  173. name=f"agent_{model_name}",
  174. llm_config={
  175. "config_list": config_list,
  176. "timeout": 120,
  177. "cache_seed": None,
  178. },
  179. )
  180. # Use a unique DB path for each model
  181. db_path = f"./tmp/db_{model_name.replace('/','_').replace('-','_')}"
  182. teachability = Teachability(
  183. verbosity=1,
  184. reset_db=True, # Clears any prior memos each run
  185. path_to_db_dir=db_path,
  186. recall_threshold=1.3,
  187. )
  188. teachability.add_to_agent(agent)
  189. return agent
  190. ###############################################################################
  191. # 4) Main Demo
  192. ###############################################################################
  193. def main():
  194. # Hardcoded sports betting text for "teaching"
  195. sports_betting_text = """\
  196. A spread bet in sports betting is a wager based on the margin of victory in a game,
  197. commonly used in popular sports like football and basketball. When you bet on the spread,
  198. you're predicting how much a team will win or lose by.
  199. If you bet on the favorite (shown with a '-' sign), that team must win by more than the
  200. specified point spread for your bet to succeed. If you bet on the underdog (shown with a '+' sign),
  201. they can lose by fewer points than the spread — or win outright — for your bet to win.
  202. The spread helps balance the odds between teams, making the bet more about predicting
  203. the margin rather than just picking a winner.
  204. Example: If the Bears are -6.5 points against the Packers, the Bears must win by more than 6.5
  205. points for a Bears -6.5 bet to cash. If the game ends with the Bears winning by exactly 6 points,
  206. that bet loses. If the underdog (Packers) loses by fewer than 6.5, an underdog bet would win.
  207. """
  208. # Create one user proxy. We'll call user.initiate_chat(...) ourselves.
  209. user = UserProxyAgent(
  210. name="user",
  211. human_input_mode="NEVER", # We handle input() in Python code
  212. max_consecutive_auto_reply=0,
  213. )
  214. # Build an agent for each model
  215. agents = {}
  216. for m in model_list:
  217. agents[m] = build_agent_for_model(m)
  218. # 4a) Teach each agent the sports-betting text once
  219. for model_name, agent in agents.items():
  220. print(f"\n>>> Teaching {model_name} the sports-betting text...\n")
  221. teach_resp = user.initiate_chat(agent, message=sports_betting_text, clear_history=True)
  222. print(f"{model_name} replied:", teach_resp.content)
  223. # 4b) Now we do a Q&A loop: user types question, all agents respond
  224. print("\n=== Multi-Model Q&A ===")
  225. print("Type 'exit' to quit.\n")
  226. while True:
  227. q = input("You: ").strip()
  228. if q.lower() in ["exit", "quit"]:
  229. print("Goodbye!")
  230. break
  231. for model_name, agent in agents.items():
  232. # Start a new chat to confirm it can recall the taught text
  233. # Or you can do clear_history=False so it accumulates Q&A
  234. answer = user.initiate_chat(agent, message=q, clear_history=True)
  235. print(f"\n[{model_name} responds]\n{answer.content}\n")
  236. # Optional: small delay if you want to separate them visually
  237. time.sleep(1)
  238. if __name__ == "__main__":
  239. main()
  240. # %%
  241. import requests
  242. resp = requests.post('https://textbelt.com/text', {
  243. 'phone': '9163802941',
  244. 'message': 'PD Pipe Line Part 3 Done',
  245. 'key': '138adc496234ca311154757db147f552afa8ba83FfrCKJ36kTJNXq65nlsvvF4Pu',
  246. })
  247. print(resp.json())
  248. # %%
  249. 1=2
  250. # %%
  251. # %%
  252. # old working code
  253. # %%
  254. import os
  255. import requests
  256. import json
  257. import logging
  258. import pandas as pd
  259. import time
  260. from typing import Dict, List, Optional, Union
  261. from dataclasses import dataclass
  262. from enum import Enum
  263. # Configure logging
  264. logging.basicConfig(
  265. level=logging.INFO,
  266. format='%(asctime)s - %(levelname)s - %(message)s',
  267. datefmt='%Y-%m-%d %H:%M:%S'
  268. )
  269. class APIError(Exception):
  270. """Custom exception for API-related errors"""
  271. pass
  272. class ValidationError(Exception):
  273. """Custom exception for response validation errors"""
  274. pass
  275. class BrainStimStatus(str, Enum):
  276. YES = "Yes"
  277. NO = "No"
  278. @dataclass
  279. class StimulationParameters:
  280. frequency: Optional[str] = None
  281. intensity: Optional[str] = None
  282. duration: Optional[str] = None
  283. @dataclass
  284. class StimulationDetails:
  285. primary_type: Optional[str] = None
  286. is_noninvasive: Optional[bool] = None
  287. primary_target: Optional[str] = None
  288. secondary_targets: List[str] = None
  289. stimulation_parameters: StimulationParameters = None
  290. def __post_init__(self):
  291. if self.secondary_targets is None:
  292. self.secondary_targets = []
  293. if self.stimulation_parameters is None:
  294. self.stimulation_parameters = StimulationParameters()
  295. @dataclass
  296. class BrainStimResponse:
  297. brain_stimulation_used: BrainStimStatus
  298. stimulation_details: StimulationDetails
  299. confidence_level: str
  300. relevant_quotes: List[str]
  301. class BrainStimAnalyzer:
  302. def __init__(self, api_key: str, site_url: str, app_name: str):
  303. self.api_key = api_key
  304. self.site_url = site_url
  305. self.app_name = app_name
  306. self.models = {
  307. "gpt-4": {
  308. "requires_image": False,
  309. "max_tokens": 4096,
  310. "temperature": 0.5,
  311. "instructions": "Return ONLY a JSON object with no additional explanatory text."
  312. },
  313. "anthropic/claude-3.5-sonnet": {
  314. "requires_image": False,
  315. "max_tokens": 10000,
  316. "temperature": 0.5,
  317. "instructions": "Return ONLY a JSON object with no additional explanatory text."
  318. },
  319. "meta-llama/llama-3-70b-instruct": {
  320. "requires_image": False,
  321. "max_tokens": 4096,
  322. "temperature": 0.5,
  323. "instructions": "Return ONLY a JSON object with no additional explanatory text."
  324. }
  325. }
  326. def extract_json_from_text(self, text: str) -> Optional[str]:
  327. """Extract JSON object from text that may contain additional content"""
  328. try:
  329. # Look for JSON-like content between curly braces
  330. start = text.find('{')
  331. end = text.rfind('}')
  332. if start != -1 and end != -1:
  333. json_str = text[start:end+1]
  334. # Validate it's proper JSON
  335. json.loads(json_str)
  336. return json_str
  337. except json.JSONDecodeError:
  338. pass
  339. return None
  340. def validate_brain_stim_response(self, response_json: Dict) -> BrainStimResponse:
  341. """Validate response structure and content"""
  342. required_fields = [
  343. "brain_stimulation_used",
  344. "stimulation_details",
  345. "confidence_level",
  346. "relevant_quotes"
  347. ]
  348. # Check required fields
  349. for field in required_fields:
  350. if field not in response_json:
  351. raise ValidationError(f"Missing required field: {field}")
  352. # Validate brain_stimulation_used
  353. if not isinstance(response_json["brain_stimulation_used"], str):
  354. raise ValidationError("brain_stimulation_used must be string")
  355. if response_json["brain_stimulation_used"] not in ["Yes", "No"]:
  356. raise ValidationError("brain_stimulation_used must be 'Yes' or 'No'")
  357. # Convert to BrainStimResponse object
  358. stim_params = StimulationParameters(**response_json["stimulation_details"]["stimulation_parameters"])
  359. stim_details = StimulationDetails(
  360. primary_type=response_json["stimulation_details"]["primary_type"],
  361. is_noninvasive=response_json["stimulation_details"]["is_noninvasive"],
  362. primary_target=response_json["stimulation_details"]["primary_target"],
  363. secondary_targets=response_json["stimulation_details"]["secondary_targets"],
  364. stimulation_parameters=stim_params
  365. )
  366. return BrainStimResponse(
  367. brain_stimulation_used=BrainStimStatus(response_json["brain_stimulation_used"]),
  368. stimulation_details=stim_details,
  369. confidence_level=response_json["confidence_level"],
  370. relevant_quotes=response_json["relevant_quotes"]
  371. )
  372. def send_request(self, model_name: str, prompt: str, max_retries: int = 3) -> BrainStimResponse:
  373. """Send request to API with retry logic"""
  374. if model_name not in self.models:
  375. raise ValueError(f"Unknown model: {model_name}")
  376. model_config = self.models[model_name]
  377. prompt = f"{model_config['instructions']}\n{prompt}"
  378. retries = 0
  379. backoff = 2 # seconds
  380. while retries < max_retries:
  381. try:
  382. response = requests.post(
  383. url="https://openrouter.ai/api/v1/chat/completions",
  384. headers={
  385. "Authorization": f"Bearer {self.api_key}",
  386. "HTTP-Referer": self.site_url,
  387. "X-Title": self.app_name,
  388. "Content-Type": "application/json"
  389. },
  390. json={
  391. "model": model_name,
  392. "messages": [{"role": "user", "content": prompt}],
  393. "temperature": model_config["temperature"],
  394. "max_tokens": model_config["max_tokens"]
  395. },
  396. timeout=60
  397. )
  398. logging.info(f"Sent request to {model_name}")
  399. logging.debug(f"Raw response: {response.text}")
  400. if response.status_code == 200:
  401. try:
  402. response_data = response.json()
  403. # Check if 'choices' and necessary keys exist in the response
  404. if 'choices' in response_data and len(response_data['choices']) > 0:
  405. choice = response_data['choices'][0]
  406. if 'message' in choice and 'content' in choice['message']:
  407. content = choice['message']['content'].strip()
  408. # Try to parse as JSON directly first
  409. try:
  410. response_json = json.loads(content)
  411. except json.JSONDecodeError:
  412. # If direct parsing fails, try to extract JSON from text
  413. json_str = self.extract_json_from_text(content)
  414. if not json_str:
  415. raise ValidationError(f"Could not extract valid JSON from response: {content}")
  416. response_json = json.loads(json_str)
  417. # Validate response structure and content
  418. return self.validate_brain_stim_response(response_json)
  419. else:
  420. raise ValidationError("Response does not contain 'message' or 'content' keys.")
  421. else:
  422. raise ValidationError("Response does not contain 'choices' or it's empty.")
  423. except (KeyError, IndexError) as e:
  424. logging.error(f"Error extracting response content: {e}")
  425. raise ValidationError(f"Invalid response structure from {model_name}")
  426. elif response.status_code in [429, 500, 502, 503, 504]:
  427. if retries == max_retries - 1:
  428. raise APIError(f"Max retries reached for {model_name}")
  429. retries += 1
  430. time.sleep(backoff)
  431. backoff *= 2
  432. continue
  433. else:
  434. raise APIError(f"Request failed with status {response.status_code}: {response.text}")
  435. except requests.exceptions.RequestException as e:
  436. if retries == max_retries - 1:
  437. raise APIError(f"Request failed after {max_retries} retries: {str(e)}")
  438. retries += 1
  439. time.sleep(backoff)
  440. backoff *= 2
  441. continue
  442. raise APIError(f"Failed to get valid response from {model_name}")
  443. class BrainStimAnalysis:
  444. def __init__(self, api_key: str, site_url: str, app_name: str):
  445. self.analyzer = BrainStimAnalyzer(api_key, site_url, app_name)
  446. def process_trial(self, df: pd.DataFrame, output_dir: str = '03_data_ai'):
  447. """Process trial data and save results"""
  448. os.makedirs(output_dir, exist_ok=True)
  449. result_df = df.copy()
  450. # Initialize response arrays
  451. responses = []
  452. # Process each row
  453. for idx, row in df.iterrows():
  454. context = row.get('DetailedDescription', row.get('BriefSummary', ''))
  455. prompt = self.generate_prompt(context)
  456. # Get responses from each model
  457. for model_name in self.analyzer.models.keys():
  458. try:
  459. response = self.analyzer.send_request(model_name, prompt)
  460. responses.append({
  461. "Model": model_name,
  462. "Response": response
  463. })
  464. # Update DataFrame with response data
  465. self.update_dataframe(result_df, idx, model_name, response)
  466. except (APIError, ValidationError) as e:
  467. logging.error(f"Error processing row {idx} with model {model_name}: {str(e)}")
  468. continue
  469. # Save results
  470. self.save_results(result_df, output_dir)
  471. return result_df, responses
  472. def generate_prompt(self, context: str) -> str:
  473. """Generate analysis prompt from context"""
  474. return f"""
  475. Analyze whether brain stimulation was used in this trial. If so, provide details.
  476. IMPORTANT: Respond ONLY with a JSON object in the EXACT format below. Do not include any additional text or explanations.
  477. {{
  478. "brain_stimulation_used": "Yes" or "No",
  479. "stimulation_details": {{
  480. "primary_type": "e.g., tDCS, TMS, tACS, DBS, etc." or null,
  481. "is_noninvasive": true or false,
  482. "primary_target": "Primary brain region or null",
  483. "secondary_targets": ["List of secondary regions"] or [],
  484. "stimulation_parameters": {{
  485. "frequency": "e.g., 10Hz" or null,
  486. "intensity": "e.g., 2mA" or null,
  487. "duration": "e.g., 20 minutes" or null
  488. }}
  489. }},
  490. "confidence_level": "High", "Medium", or "Low",
  491. "relevant_quotes": ["Direct quotes supporting the analysis"]
  492. }}
  493. Context:
  494. {context}
  495. """
  496. def update_dataframe(self, df: pd.DataFrame, idx: int, model_name: str, response: BrainStimResponse):
  497. """Update DataFrame with response data"""
  498. suffix = model_name.replace("/", "_").replace("-", "_")
  499. # Update brain stimulation status
  500. df.at[idx, f"brain_stimulation_used_{suffix}"] = response.brain_stimulation_used.value
  501. # Update stimulation details
  502. if response.stimulation_details:
  503. df.at[idx, f"stimulation_details_primary_type_{suffix}"] = response.stimulation_details.primary_type
  504. df.at[idx, f"stimulation_details_is_noninvasive_{suffix}"] = response.stimulation_details.is_noninvasive
  505. df.at[idx, f"stimulation_details_primary_target_{suffix}"] = response.stimulation_details.primary_target
  506. df.at[idx, f"stimulation_details_secondary_targets_{suffix}"] = json.dumps(response.stimulation_details.secondary_targets)
  507. # Update parameters
  508. params = response.stimulation_details.stimulation_parameters
  509. df.at[idx, f"stimulation_details_parameters_frequency_{suffix}"] = params.frequency
  510. df.at[idx, f"stimulation_details_parameters_intensity_{suffix}"] = params.intensity
  511. df.at[idx, f"stimulation_details_parameters_duration_{suffix}"] = params.duration
  512. # Update confidence and quotes
  513. df.at[idx, f"confidence_level_{suffix}"] = response.confidence_level
  514. df.at[idx, f"relevant_quotes_{suffix}"] = json.dumps(response.relevant_quotes)
  515. def save_results(self, df: pd.DataFrame, output_dir: str):
  516. """Save results to files"""
  517. df.to_excel(f'{output_dir}/part_3_covariates_done.xlsx', index=False)
  518. df.to_parquet(f'{output_dir}/part_3_covariates_done.parquet', index=False)
  519. def main():
  520. # Configuration
  521. api_key = openrouter.api_key
  522. site_url = 'https://pd-research.com'
  523. app_name = 'PD Research'
  524. if not api_key:
  525. logging.error("API_KEY is not set. Please set the OPENROUTER_API_KEY environment variable.")
  526. exit(1)
  527. try:
  528. # Load your DataFrame (replace with actual loading logic)
  529. df = filtered_df_covariates ## Update with your file path
  530. # Initialize and run analysis
  531. analysis = BrainStimAnalysis(api_key, site_url, app_name)
  532. result_df, responses = analysis.process_trial(df)
  533. # Save result_df and responses to Excel and Parquet formats
  534. output_dir = '03_data_ai'
  535. os.makedirs(output_dir, exist_ok=True)
  536. # Save result_df
  537. result_df.to_excel(f'{output_dir}/part_3_covariates_done_v2.xlsx', index=False)
  538. result_df.to_parquet(f'{output_dir}/part_3_covariates_done_v2.parquet', index=False)
  539. # Convert responses to DataFrame and save
  540. responses_df = pd.DataFrame(responses)
  541. responses_df.to_excel(f'{output_dir}/responses_done_v2.xlsx', index=False)
  542. responses_df.to_parquet(f'{output_dir}/responses_done_v2.parquet', index=False)
  543. logging.info(f"Results saved to {output_dir}/part_3_covariates_done_v2.xlsx and {output_dir}/responses_done_v2.xlsx")
  544. # Display results
  545. print("\n=== Updated LLM Responses ===")
  546. print(result_df)
  547. # Optional: Display using PrettyTable
  548. try:
  549. from prettytable import PrettyTable
  550. table = PrettyTable()
  551. table.field_names = ["Model", "Response"]
  552. for resp in responses:
  553. table.add_row([resp["Model"], resp["Response"]])
  554. print("\n=== LLM Responses (PrettyTable) ===")
  555. print(table)
  556. except ImportError:
  557. logging.warning("PrettyTable not installed. Skipping table formatting.")
  558. except Exception as e:
  559. logging.error(f"Error in main execution: {str(e)}")
  560. raise
  561. # first_five.to_excel('filtered_df_covariates_updated.xlsx', index=False)
  562. # logging.info("Updated DataFrame has been saved to filtered_df_covariates_updated.xlsx")
  563. if __name__ == "__main__":
  564. main()
  565. # %% [markdown]
  566. # old code working
  567. # %%
  568. # Save the updated DataFrame to files in folder '03_data_ai'
  569. responses.to_excel('03_data_ai/part_3_covariates_done_v2.xlsx', index=False)
  570. # result_df.to_parquet('03_data_ai/part_3_covariates_done_v2.parquet', index=False)
  571. # %%
  572. 1=2
  573. # %%
  574. import os
  575. import requests
  576. import json
  577. import logging
  578. import pandas as pd
  579. import time
  580. # Configure logging
  581. logging.basicConfig(level=logging.INFO, format='%(levelname)s:%(message)s')
  582. # Configuration Variables
  583. API_KEY = openrouter.api_key
  584. if not API_KEY:
  585. logging.error("API_KEY is not set. Please set the OPENROUTER_API_KEY environment variable.")
  586. exit(1)
  587. SITE_URL = 'https://pd-research.com' # Your site's URL
  588. APP_NAME = 'PD Research' # Your application's name
  589. # Define the models to query with their specific settings
  590. MODELS = {
  591. "gpt-4": {
  592. "requires_image": False,
  593. "payload_modifier": lambda content: content, # No modification needed
  594. "max_tokens": 4096, # Example max_tokens for GPT-4
  595. "temperature": 0.5 # Lower temperature for accuracy
  596. },
  597. "anthropic/claude-3.5-sonnet": {
  598. "requires_image": False, # Changed to False as per your requirement
  599. "payload_modifier": lambda content: content, # No modification needed
  600. "max_tokens": 10000, # Example max_tokens for Claude 3.5
  601. "temperature": 0.5 # Lower temperature for accuracy
  602. },
  603. "meta-llama/llama-3-70b-instruct": {
  604. "requires_image": False,
  605. "payload_modifier": lambda content: content, # No modification needed
  606. "max_tokens": 4096, # Example max_tokens for Qwen
  607. "temperature": 0.5 # Lower temperature for accuracy
  608. }
  609. }
  610. # Define colors for each model (Optional: Requires colorama)
  611. try:
  612. from colorama import Fore, Style
  613. COLORAMA_AVAILABLE = True
  614. except ImportError:
  615. COLORAMA_AVAILABLE = False
  616. logging.warning("Colorama not installed. Responses will not be color-coded.")
  617. MODEL_COLORS = {
  618. "gpt-4": Fore.BLUE if COLORAMA_AVAILABLE else "",
  619. "anthropic/claude-3.5-sonnet": Fore.MAGENTA if COLORAMA_AVAILABLE else "",
  620. "meta-llama/llama-3-70b-instruct": Fore.GREEN if COLORAMA_AVAILABLE else ""
  621. }
  622. def send_request(model_name, prompt):
  623. """
  624. Sends a request to the specified model with the given prompt.
  625. Parameters:
  626. model_name (str): The name of the model to query.
  627. prompt (str): The text prompt/question.
  628. Returns:
  629. str: The assistant's reply or an error message.
  630. """
  631. logging.info(f"Sending request to model: {model_name}")
  632. # For text-only models, no modification is needed
  633. content = prompt
  634. modified_content = MODELS[model_name]["payload_modifier"](content)
  635. # Construct the payload with per-model settings
  636. payload = {
  637. "model": model_name,
  638. "messages": [
  639. {
  640. "role": "user",
  641. "content": modified_content
  642. }
  643. ],
  644. "top_p": 1,
  645. "temperature": MODELS[model_name]["temperature"], # Use per-model temperature
  646. "frequency_penalty": 0,
  647. "presence_penalty": 0,
  648. "repetition_penalty": 1,
  649. "top_k": 0,
  650. "max_tokens": MODELS[model_name]["max_tokens"], # Use per-model max_tokens
  651. }
  652. retries = 3
  653. backoff = 2 # seconds
  654. for attempt in range(1, retries + 1):
  655. try:
  656. # Make the POST request to the OpenRouter API
  657. response = requests.post(
  658. url="https://openrouter.ai/api/v1/chat/completions",
  659. headers={
  660. "Authorization": f"Bearer {API_KEY}",
  661. "HTTP-Referer": SITE_URL, # Optional: Include if you want your app featured
  662. "X-Title": APP_NAME, # Optional: Shows your app name on OpenRouter rankings
  663. "Content-Type": "application/json" # Ensure content type is set
  664. },
  665. data=json.dumps(payload),
  666. timeout=60 # Increased timeout for models that may take longer
  667. )
  668. logging.info("API request sent. Awaiting response...")
  669. # Check Content-Type
  670. content_type = response.headers.get('Content-Type', '')
  671. logging.debug(f"Response Content-Type: {content_type}")
  672. if 'application/json' not in content_type:
  673. logging.error(f"Unexpected Content-Type: {content_type}")
  674. logging.error(f"Response: {response.text}")
  675. return "Error: Unexpected Content-Type"
  676. # Check if the request was successful
  677. if response.status_code == 200:
  678. logging.info(f"Received successful response from {model_name}.")
  679. try:
  680. response_data = response.json()
  681. # Extract the assistant's reply
  682. assistant_reply = response_data['choices'][0]['message']['content'].strip()
  683. logging.info(f"Assistant's reply from {model_name}: {assistant_reply}")
  684. return assistant_reply
  685. except (KeyError, IndexError) as e:
  686. logging.error(f"Error extracting assistant's reply from {model_name}: {e}")
  687. logging.debug(f"Full response from {model_name}: {json.dumps(response_data, indent=4)}")
  688. return "Error extracting reply."
  689. elif response.status_code in [429, 500, 502, 503, 504]:
  690. # Retry for rate limiting or server errors
  691. logging.warning(f"Attempt {attempt} failed with status code {response.status_code}. Retrying in {backoff} seconds...")
  692. time.sleep(backoff)
  693. backoff *= 2
  694. else:
  695. logging.error(f"Request to {model_name} failed with status code {response.status_code}")
  696. logging.error(f"Response: {response.text}")
  697. return f"Error: Received status code {response.status_code}"
  698. except requests.exceptions.RequestException as e:
  699. logging.error(f"Attempt {attempt} failed with exception: {e}")
  700. if attempt < retries:
  701. logging.info(f"Retrying in {backoff} seconds...")
  702. time.sleep(backoff)
  703. backoff *= 2
  704. else:
  705. logging.error("All retry attempts failed.")
  706. return f"Error: {e}"
  707. return "Error: Unable to retrieve response."
  708. def main():
  709. # Load your DataFrame
  710. # Replace the following line with how you load your DataFrame
  711. # For example, if it's a CSV:
  712. # filtered_df_covariates = pd.read_csv('filtered_df_covariates.csv')
  713. # For demonstration, let's assume filtered_df_covariates is already loaded
  714. # Here's a placeholder:
  715. # filtered_df_covariates = pd.DataFrame(...) # Your DataFrame loading logic
  716. # **Important:** Replace the following line with your actual DataFrame loading method
  717. # For example:
  718. # filtered_df_covariates = pd.read_csv('your_file.csv')
  719. # Ensure that 'filtered_df_covariates' is defined before proceeding
  720. try:
  721. filtered_df_covariates # Check if the DataFrame is already loaded
  722. except NameError:
  723. logging.error("DataFrame 'filtered_df_covariates' is not defined. Please load it before running the script.")
  724. exit(1)
  725. # Define the prompt
  726. prompt_template = """
  727. Analyze whether brain stimulation was used in this trial. If so, provide details.
  728. IMPORTANT: Respond ONLY with a JSON object in the EXACT format below. Do not include any additional text or explanations.
  729. {
  730. "brain_stimulation_used": "Yes" or "No",
  731. "stimulation_details": {
  732. "primary_type": "e.g., tDCS, TMS, tACS, DBS, etc." or null,
  733. "is_noninvasive": true or false,
  734. "primary_target": "Primary brain region or null",
  735. "secondary_targets": ["List of secondary regions"] or [],
  736. "stimulation_parameters": {
  737. "frequency": "e.g., 10Hz" or null,
  738. "intensity": "e.g., 2mA" or null,
  739. "duration": "e.g., 20 minutes" or null
  740. }
  741. },
  742. "confidence_level": "High", "Medium", or "Low",
  743. "relevant_quotes": ["Direct quotes supporting the analysis"]
  744. }
  745. """
  746. # Select the first 5 rows
  747. first_five = filtered_df_covariates.copy()
  748. # Define new columns for JSON fields with model suffixes
  749. # We'll create columns dynamically as we process each model
  750. # Alternatively, initialize them if you know the models beforehand
  751. # Initialize the responses list
  752. responses = []
  753. # Iterate over the first 5 rows
  754. for idx, row in first_five.iterrows():
  755. # Extract relevant information from the row to include in the prompt
  756. # Customize this based on which columns provide context for the analysis
  757. # For example, using 'DetailedDescription' or 'FullAnalysis'
  758. context = row['DetailedDescription'] if 'DetailedDescription' in row else row['BriefSummary']
  759. # Construct the full prompt by appending context to the prompt template
  760. prompt = f"{prompt_template}\n\nContext:\n{context}"
  761. # Send the prompt to each model and store responses
  762. for model in MODELS.keys():
  763. response = send_request(model, prompt)
  764. responses.append({
  765. "Model": model,
  766. "Response": response
  767. })
  768. # Check if the response is a JSON object
  769. try:
  770. response_json = json.loads(response)
  771. except json.JSONDecodeError:
  772. logging.error(f"Response from {model} is not valid JSON. Response: {response}")
  773. response_json = {}
  774. # Define column suffix based on model name
  775. suffix = model.replace("/", "_").replace("-", "_")
  776. # Populate the new columns with the JSON data
  777. if "brain_stimulation_used" in response_json:
  778. first_five.at[idx, f"brain_stimulation_used_{suffix}"] = response_json.get("brain_stimulation_used", None)
  779. else:
  780. first_five.at[idx, f"brain_stimulation_used_{suffix}"] = None
  781. if "stimulation_details" in response_json and isinstance(response_json["stimulation_details"], dict):
  782. stim_details = response_json["stimulation_details"]
  783. first_five.at[idx, f"stimulation_details_primary_type_{suffix}"] = stim_details.get("primary_type", None)
  784. first_five.at[idx, f"stimulation_details_is_noninvasive_{suffix}"] = stim_details.get("is_noninvasive", None)
  785. first_five.at[idx, f"stimulation_details_primary_target_{suffix}"] = stim_details.get("primary_target", None)
  786. # Assign as JSON string to avoid issues with lists
  787. secondary_targets = stim_details.get("secondary_targets", [])
  788. first_five.at[idx, f"stimulation_details_secondary_targets_{suffix}"] = json.dumps(secondary_targets)
  789. stim_params = stim_details.get("stimulation_parameters", {})
  790. first_five.at[idx, f"stimulation_details_stimulation_parameters_frequency_{suffix}"] = stim_params.get("frequency", None)
  791. first_five.at[idx, f"stimulation_details_stimulation_parameters_intensity_{suffix}"] = stim_params.get("intensity", None)
  792. first_five.at[idx, f"stimulation_details_stimulation_parameters_duration_{suffix}"] = stim_params.get("duration", None)
  793. else:
  794. # If 'stimulation_details' is missing or not a dict, set defaults
  795. first_five.at[idx, f"stimulation_details_primary_type_{suffix}"] = None
  796. first_five.at[idx, f"stimulation_details_is_noninvasive_{suffix}"] = None
  797. first_five.at[idx, f"stimulation_details_primary_target_{suffix}"] = None
  798. first_five.at[idx, f"stimulation_details_secondary_targets_{suffix}"] = json.dumps([])
  799. first_five.at[idx, f"stimulation_details_stimulation_parameters_frequency_{suffix}"] = None
  800. first_five.at[idx, f"stimulation_details_stimulation_parameters_intensity_{suffix}"] = None
  801. first_five.at[idx, f"stimulation_details_stimulation_parameters_duration_{suffix}"] = None
  802. if "confidence_level" in response_json:
  803. first_five.at[idx, f"confidence_level_{suffix}"] = response_json.get("confidence_level", None)
  804. else:
  805. first_five.at[idx, f"confidence_level_{suffix}"] = None
  806. if "relevant_quotes" in response_json:
  807. # Assign as JSON string to avoid issues with lists
  808. relevant_quotes = response_json.get("relevant_quotes", [])
  809. first_five.at[idx, f"relevant_quotes_{suffix}"] = json.dumps(relevant_quotes)
  810. else:
  811. first_five.at[idx, f"relevant_quotes_{suffix}"] = json.dumps([])
  812. # Display the updated DataFrame
  813. print("\n=== Updated LLM Responses ===")
  814. # Select only the new columns for display
  815. new_columns_display = []
  816. for model in MODELS.keys():
  817. suffix = model.replace("/", "_").replace("-", "_")
  818. new_columns_display.extend([
  819. f"brain_stimulation_used_{suffix}",
  820. f"stimulation_details_primary_type_{suffix}",
  821. f"stimulation_details_is_noninvasive_{suffix}",
  822. f"stimulation_details_primary_target_{suffix}",
  823. f"stimulation_details_secondary_targets_{suffix}",
  824. f"stimulation_details_stimulation_parameters_frequency_{suffix}",
  825. f"stimulation_details_stimulation_parameters_intensity_{suffix}",
  826. f"stimulation_details_stimulation_parameters_duration_{suffix}",
  827. f"confidence_level_{suffix}",
  828. f"relevant_quotes_{suffix}"
  829. ])
  830. print(first_five[new_columns_display])
  831. # Optional: Using PrettyTable for better console display
  832. try:
  833. from prettytable import PrettyTable
  834. table = PrettyTable()
  835. table.field_names = ["Model", "Response"]
  836. for resp in responses:
  837. table.add_row([resp["Model"], resp["Response"]])
  838. print("\n=== LLM Responses (PrettyTable) ===")
  839. print(table)
  840. except ImportError:
  841. logging.warning("PrettyTable not installed. Skipping table formatting.")
  842. # Optional: Using colorama for color-coded console output
  843. if COLORAMA_AVAILABLE:
  844. print("\n=== LLM Responses (Color-Coded) ===")
  845. for resp in responses:
  846. color = MODEL_COLORS.get(resp["Model"], Fore.WHITE)
  847. print(f"{color}{resp['Model']}: {resp['Response']}{Style.RESET_ALL}")
  848. else:
  849. logging.info("Colorama not available. Skipping color-coded output.")
  850. # Optional: Save the updated DataFrame to a file
  851. # Uncomment the following lines to save as CSV or Excel
  852. # first_five.to_csv('filtered_df_covariates_updated.csv', index=False)
  853. # Ensure the new columns are in the DataFrame
  854. for model in MODELS.keys():
  855. suffix = model.replace("/", "_").replace("-", "_")
  856. new_columns = [
  857. f"brain_stimulation_used_{suffix}",
  858. f"stimulation_details_primary_type_{suffix}",
  859. f"stimulation_details_is_noninvasive_{suffix}",
  860. f"stimulation_details_primary_target_{suffix}",
  861. f"stimulation_details_secondary_targets_{suffix}",
  862. f"stimulation_details_stimulation_parameters_frequency_{suffix}",
  863. f"stimulation_details_stimulation_parameters_intensity_{suffix}",
  864. f"stimulation_details_stimulation_parameters_duration_{suffix}",
  865. f"confidence_level_{suffix}",
  866. f"relevant_quotes_{suffix}"
  867. ]
  868. for col in new_columns:
  869. if col not in first_five.columns:
  870. first_five[col] = None
  871. # Ensure the directory '03_data_ai' exists
  872. os.makedirs('03_data_ai', exist_ok=True)
  873. # Save the updated DataFrame to files in folder '03_data_ai'
  874. first_five.to_excel('03_data_ai/part_3_covariates_done.xlsx', index=False)
  875. first_five.to_parquet('03_data_ai/part_3_covariates_done.parquet', index=False)
  876. # first_five.to_excel('filtered_df_covariates_updated.xlsx', index=False)
  877. # logging.info("Updated DataFrame has been saved to filtered_df_covariates_updated.xlsx")
  878. if __name__ == "__main__":
  879. main()
  880. # %%
  881. # # Save the DataFrame to a CSV file
  882. # filtered_df_covariates.to_csv('03_data_ai/part_3_covariates_done.csv', index=False)
  883. # filtered_df_covariates.to_excel('03_data_ai/part_3_covariates_done.xlsx', index=False)
  884. # filtered_df_covariates.to_parquet('03_data_ai/part_3_covariates_done.parquet', index=False)
  885. # %%
  886. # Save the DataFrame to an Excel file
  887. # %%
  888. 1=2
  889. # %%
  890. # WORKING - BELOW÷
  891. # %%
  892. # import os
  893. # import requests
  894. # import json
  895. # import logging
  896. # import pandas as pd
  897. # import time
  898. # # Configure logging
  899. # # logging.basicConfig(level=logging.INFO, format='%(levelname)s:%(message)s')
  900. # SITE_URL = 'https://pd-research.com' # Your site's URL
  901. # APP_NAME = 'PD Research' # Your application's name
  902. # # Define the models to query with their specific settings
  903. # MODELS = {
  904. # "gpt-4": {
  905. # "requires_image": False,
  906. # "payload_modifier": lambda content: content, # No modification needed
  907. # "max_tokens": 4096, # Example max_tokens for GPT-4
  908. # "temperature": 0.5 # Lower temperature for accuracy
  909. # },
  910. # "anthropic/claude-3.5-sonnet": {
  911. # "requires_image": False, # Changed to False as per your requirement
  912. # "payload_modifier": lambda content: content, # No modification needed
  913. # "max_tokens": 10000, # Example max_tokens for Claude 3.5
  914. # "temperature": 0.5 # Lower temperature for accuracy
  915. # },
  916. # "qwen/qwen-2.5-72b-instruct": {
  917. # "requires_image": False,
  918. # "payload_modifier": lambda content: content, # No modification needed
  919. # "max_tokens": 4096, # Example max_tokens for Qwen
  920. # "temperature": 0.5 # Lower temperature for accuracy
  921. # }
  922. # }
  923. # # Define colors for each model (Optional: Requires colorama)
  924. # try:
  925. # from colorama import Fore, Style
  926. # COLORAMA_AVAILABLE = True
  927. # except ImportError:
  928. # COLORAMA_AVAILABLE = False
  929. # logging.warning("Colorama not installed. Responses will not be color-coded.")
  930. # MODEL_COLORS = {
  931. # "gpt-4": Fore.BLUE if COLORAMA_AVAILABLE else "",
  932. # "anthropic/claude-3.5-sonnet": Fore.MAGENTA if COLORAMA_AVAILABLE else "",
  933. # "qwen/qwen-2.5-72b-instruct": Fore.GREEN if COLORAMA_AVAILABLE else ""
  934. # }
  935. # def send_request(model_name, prompt):
  936. # """
  937. # Sends a request to the specified model with the given prompt.
  938. # Parameters:
  939. # model_name (str): The name of the model to query.
  940. # prompt (str): The text prompt/question.
  941. # Returns:
  942. # str: The assistant's reply or an error message.
  943. # """
  944. # logging.info(f"Sending request to model: {model_name}")
  945. # # For text-only models, no modification is needed
  946. # content = prompt
  947. # modified_content = MODELS[model_name]["payload_modifier"](content)
  948. # # Construct the payload with per-model settings
  949. # payload = {
  950. # "model": model_name,
  951. # "messages": [
  952. # {
  953. # "role": "user",
  954. # "content": modified_content
  955. # }
  956. # ],
  957. # "top_p": 1,
  958. # "temperature": MODELS[model_name]["temperature"], # Use per-model temperature
  959. # "frequency_penalty": 0,
  960. # "presence_penalty": 0,
  961. # "repetition_penalty": 1,
  962. # "top_k": 0,
  963. # "max_tokens": MODELS[model_name]["max_tokens"], # Use per-model max_tokens
  964. # }
  965. # retries = 3
  966. # backoff = 2 # seconds
  967. # for attempt in range(1, retries + 1):
  968. # try:
  969. # # Make the POST request to the OpenRouter API
  970. # response = requests.post(
  971. # url="https://openrouter.ai/api/v1/chat/completions",
  972. # headers={
  973. # "Authorization": f"Bearer {API_KEY}",
  974. # "HTTP-Referer": SITE_URL, # Optional: Include if you want your app featured
  975. # "X-Title": APP_NAME, # Optional: Shows your app name on OpenRouter rankings
  976. # "Content-Type": "application/json" # Ensure content type is set
  977. # },
  978. # data=json.dumps(payload),
  979. # timeout=60 # Increased timeout for models that may take longer
  980. # )
  981. # logging.info("API request sent. Awaiting response...")
  982. # # Check Content-Type
  983. # content_type = response.headers.get('Content-Type', '')
  984. # logging.debug(f"Response Content-Type: {content_type}")
  985. # if 'application/json' not in content_type:
  986. # logging.error(f"Unexpected Content-Type: {content_type}")
  987. # logging.error(f"Response: {response.text}")
  988. # return "Error: Unexpected Content-Type"
  989. # # Check if the request was successful
  990. # if response.status_code == 200:
  991. # logging.info(f"Received successful response from {model_name}.")
  992. # try:
  993. # response_data = response.json()
  994. # # Extract the assistant's reply
  995. # assistant_reply = response_data['choices'][0]['message']['content'].strip()
  996. # logging.info(f"Assistant's reply from {model_name}: {assistant_reply}")
  997. # return assistant_reply
  998. # except (KeyError, IndexError) as e:
  999. # logging.error(f"Error extracting assistant's reply from {model_name}: {e}")
  1000. # logging.debug(f"Full response from {model_name}: {json.dumps(response_data, indent=4)}")
  1001. # return "Error extracting reply."
  1002. # elif response.status_code in [429, 500, 502, 503, 504]:
  1003. # # Retry for rate limiting or server errors
  1004. # logging.warning(f"Attempt {attempt} failed with status code {response.status_code}. Retrying in {backoff} seconds...")
  1005. # time.sleep(backoff)
  1006. # backoff *= 2
  1007. # else:
  1008. # logging.error(f"Request to {model_name} failed with status code {response.status_code}")
  1009. # logging.error(f"Response: {response.text}")
  1010. # return f"Error: Received status code {response.status_code}"
  1011. # except requests.exceptions.RequestException as e:
  1012. # logging.error(f"Attempt {attempt} failed with exception: {e}")
  1013. # if attempt < retries:
  1014. # logging.info(f"Retrying in {backoff} seconds...")
  1015. # time.sleep(backoff)
  1016. # backoff *= 2
  1017. # else:
  1018. # logging.error("All retry attempts failed.")
  1019. # return f"Error: {e}"
  1020. # return "Error: Unable to retrieve response."
  1021. # def main():
  1022. # # Define the prompt (no image URL since all models are text-only)
  1023. # prompt = "What is the average age Parkinson's disease starts?"
  1024. # # Prepare a list to hold the responses
  1025. # responses = []
  1026. # for model in MODELS.keys():
  1027. # reply = send_request(model, prompt)
  1028. # responses.append({
  1029. # "Model": model,
  1030. # "Response": reply
  1031. # })
  1032. # # Create a pandas DataFrame to display the responses
  1033. # df = pd.DataFrame(responses)
  1034. # # Debug: Verify the DataFrame structure
  1035. # logging.debug("DataFrame before pivoting:")
  1036. # logging.debug(df)
  1037. # # Check if 'Model' and 'Response' columns exist
  1038. # if 'Model' not in df.columns or 'Response' not in df.columns:
  1039. # logging.error("DataFrame missing 'Model' or 'Response' columns.")
  1040. # exit(1)
  1041. # # Check for any None or NaN values in 'Model' column
  1042. # if df['Model'].isnull().any():
  1043. # logging.error("Found None or NaN values in 'Model' column.")
  1044. # exit(1)
  1045. # # Set 'Model' as the index and transpose the DataFrame
  1046. # df_pivot = df.set_index('Model').transpose()
  1047. # # Display the DataFrame
  1048. # print("\n=== LLM Responses ===")
  1049. # print(df_pivot.to_string(index=False))
  1050. # # Optional: Using PrettyTable for better console display
  1051. # try:
  1052. # from prettytable import PrettyTable
  1053. # table = PrettyTable()
  1054. # table.field_names = ["Model", "Response"]
  1055. # for resp in responses:
  1056. # table.add_row([resp["Model"], resp["Response"]])
  1057. # print("\n=== LLM Responses (PrettyTable) ===")
  1058. # print(table)
  1059. # except ImportError:
  1060. # logging.warning("PrettyTable not installed. Skipping table formatting.")
  1061. # # Optional: Using colorama for color-coded console output
  1062. # if COLORAMA_AVAILABLE:
  1063. # print("\n=== LLM Responses (Color-Coded) ===")
  1064. # for resp in responses:
  1065. # color = MODEL_COLORS.get(resp["Model"], Fore.WHITE)
  1066. # print(f"{color}{resp['Model']}: {resp['Response']}{Style.RESET_ALL}")
  1067. # else:
  1068. # logging.info("Colorama not available. Skipping color-coded output.")
  1069. # if __name__ == "__main__":
  1070. # main()
  1071. # %%
  1072. # %%
  1073. # %%
  1074. import requests
  1075. resp = requests.post('https://textbelt.com/text', {
  1076. 'phone': '9163802941',
  1077. 'message': 'LLM Processing Done',
  1078. 'key': '138adc496234ca311154757db147f552afa8ba83FfrCKJ36kTJNXq65nlsvvF4Pu',
  1079. })
  1080. print(resp.json())
  1081. # %%
  1082. # %%
  1083. # %%
  1084. # %%
  1085. # %%
  1086. # %%
  1087. # %%
  1088. # %%
  1089. import os
  1090. import requests
  1091. import json
  1092. import logging
  1093. # Configure logging
  1094. logging.basicConfig(level=logging.INFO)
  1095. # Configuration Variables
  1096. API_KEY = openrouter.api_key # Ensure this environment variable is set
  1097. SITE_URL = 'https://pd-research.com' # Your site's URL
  1098. APP_NAME = 'PD Research' # Your application's name
  1099. def send_test_prompt():
  1100. logging.info("Starting the API request...")
  1101. try:
  1102. # Prepare the payload
  1103. payload = {
  1104. "model": "openai/o1-mini",
  1105. "messages": [
  1106. {"role": "user", "content": "What is the average age Parkinson's disease starts?"}
  1107. ],
  1108. "top_p": 1,
  1109. "temperature": 1,
  1110. "frequency_penalty": 0,
  1111. "presence_penalty": 0,
  1112. "repetition_penalty": 1,
  1113. "top_k": 0,
  1114. }
  1115. # Make the POST request to the OpenRouter API
  1116. response = requests.post(
  1117. url="https://openrouter.ai/api/v1/chat/completions",
  1118. headers={
  1119. "Authorization": f"Bearer {API_KEY}",
  1120. "HTTP-Referer": SITE_URL, # Optional: Include if you want your app featured
  1121. "X-Title": APP_NAME, # Optional: Shows your app name on OpenRouter rankings
  1122. },
  1123. data=json.dumps(payload)
  1124. )
  1125. logging.info("API request sent. Awaiting response...")
  1126. # Check if the request was successful
  1127. if response.status_code == 200:
  1128. logging.info("Received successful response from the API.")
  1129. response_data = response.json()
  1130. # Print the full JSON response for debugging
  1131. logging.debug("Full Response JSON:")
  1132. logging.debug(json.dumps(response_data, indent=4))
  1133. # Extract the assistant's reply
  1134. try:
  1135. assistant_reply = response_data['choices'][0]['message']['content'].strip()
  1136. logging.info(f"Assistant's reply: {assistant_reply}")
  1137. except (KeyError, IndexError) as e:
  1138. logging.error("Error extracting assistant's reply:", exc_info=True)
  1139. logging.debug("Full response:", json.dumps(response_data, indent=4))
  1140. else:
  1141. logging.error(f"Request failed with status code {response.status_code}")
  1142. logging.error(f"Response: {response.text}")
  1143. except requests.exceptions.RequestException as e:
  1144. logging.error("An error occurred while making the request:", exc_info=True)
  1145. # --- Execute the Test Prompt ---
  1146. if __name__ == "__main__":
  1147. send_test_prompt()
  1148. # %%
  1149. # %%
  1150. # %%
  1151. # %%
  1152. # %%
  1153. ß
  1154. # %%
  1155. # %%
  1156. # %%
  1157. # %%
  1158. # %%
  1159. # %%
  1160. 1=2
  1161. # %%
  1162. # %%
  1163. # %%
  1164. # %%
  1165. # %%
  1166. # %%
  1167. import pandas as pd
  1168. import openai
  1169. # Set your OpenAI API key
  1170. # openai.api_key = 'YOUR_OPENAI_API_KEY' # Replace with your actual API key
  1171. # Function to query OpenAI for each row
  1172. def get_brain_area(row):
  1173. # Prepare the prompt with the relevant data from the row
  1174. prompt = f"""Given the following study data:
  1175. {row.to_json()}
  1176. What area of the brain are they looking for?"""
  1177. try:
  1178. response = openai.chat.completions.create(
  1179. model='gpt-3.5-turbo', # Use 'gpt-4' if you have access
  1180. messages=[
  1181. {"role": "user", "content": prompt}
  1182. ],
  1183. max_tokens=100,
  1184. temperature=0.5,
  1185. )
  1186. # Extract the assistant's reply
  1187. answer = response.choices[0].message.content.strip()
  1188. # Print the response
  1189. print(f"Row {row.name} response: {answer}")
  1190. return answer
  1191. except Exception as e:
  1192. print(f"Error processing row {row.name}: {e}")
  1193. return None
  1194. # Ensure that 'filtered_df_covariates' is defined with your DataFrame
  1195. # For example:
  1196. # filtered_df_covariates = pd.read_csv('your_data.csv')
  1197. # Apply the function to each row and save the result in a new column
  1198. filtered_df_covariates['BrainArea'] = filtered_df_covariates.apply(get_brain_area, axis=1)
  1199. # %%
  1200. import pandas as pd
  1201. import openai
  1202. # Set your OpenAI API key
  1203. # openai.api_key = 'YOUR_OPENAI_API_KEY' # Replace with your actual API key
  1204. # Function to query OpenAI for each row
  1205. def get_brain_area(row):
  1206. # Prepare the prompt with the relevant data from the row
  1207. prompt = f"""Given the following study data:
  1208. {row.to_json()}
  1209. What area of the brain are they looking for?"""
  1210. try:
  1211. response = openai.chat.completions.create(
  1212. model='gpt-3.5-turbo', # Use 'gpt-4' if you have access
  1213. messages=[
  1214. {"role": "user", "content": prompt}
  1215. ],
  1216. max_tokens=100,
  1217. temperature=0.5,
  1218. )
  1219. # Extract the assistant's reply
  1220. answer = response.choices[0].message.content.strip()
  1221. # Print the response
  1222. print(f"Row {row.name} response: {answer}")
  1223. return answer
  1224. except Exception as e:
  1225. print(f"Error processing row {row.name}: {e}")
  1226. return None
  1227. # Ensure that 'filtered_df_covariates' is defined with your DataFrame
  1228. # For example:
  1229. # filtered_df_covariates = pd.read_csv('your_data.csv')
  1230. # Apply the function to each row and save the result in a new column
  1231. filtered_df_covariates['BrainArea'] = filtered_df_covariates.apply(get_brain_area, axis=1)
  1232. # %%
  1233. # %%
  1234. # import pandas as pd
  1235. # import openai
  1236. # # Set your OpenAI API key
  1237. # # Function to query OpenAI for each row
  1238. # def get_brain_area(row):
  1239. # # Prepare the prompt with the relevant data from the row
  1240. # prompt = f"""Given the following study data:
  1241. # {row.to_json()}
  1242. # What area of the brain are they looking for?"""
  1243. # try:
  1244. # response = openai.chat.completions.create(
  1245. # model='gpt-3.5-turbo', # Use 'gpt-4' if you have access
  1246. # messages=[
  1247. # {"role": "user", "content": prompt}
  1248. # ],
  1249. # max_tokens=100,
  1250. # temperature=0.5,
  1251. # )
  1252. # # Extract the assistant's reply
  1253. # answer = response.choices[0].message.content.strip()
  1254. # return answer
  1255. # except Exception as e:
  1256. # print(f"Error processing row {row.name}: {e}")
  1257. # return None
  1258. # # Ensure that 'filtered_df_covariates' is defined with your DataFrame
  1259. # # For example:
  1260. # # filtered_df_covariates = pd.read_csv('your_data.csv')
  1261. # # Apply the function to each row and save the result in a new column
  1262. # filtered_df_covariates['BrainArea'] = filtered_df_covariates.apply(get_brain_area, axis=1)
  1263. # %%
  1264. # %%
  1265. # %%
  1266. # %%
  1267. # %%
  1268. # %%
  1269. # %%
  1270. # %%
  1271. # %%
  1272. # %%
  1273. import openai
  1274. print(openai.__version__)
  1275. # %%
  1276. # %%
  1277. # %%
  1278. # %%
  1279. # %%
  1280. def _call_local_llama(self, prompt: str) -> str:
  1281. """Call local Llama instance using Ollama API"""
  1282. try:
  1283. # Construct the API endpoint
  1284. api_endpoint = f"{self.local_url}generate"
  1285. # Prepare the request payload
  1286. payload = {
  1287. "model": self.local_model,
  1288. "prompt": prompt,
  1289. "options": {
  1290. "temperature": 0.1
  1291. }
  1292. }
  1293. # Send the POST request
  1294. response = requests.post(api_endpoint, json=payload)
  1295. if response.status_code == 200:
  1296. # Extract the generated text from the response
  1297. result = response.json()
  1298. if 'generated_text' in result:
  1299. return result['generated_text']
  1300. else:
  1301. print(f"Unexpected response structure: {result}")
  1302. return ''
  1303. else:
  1304. raise Exception(f"Local LLM error: Status {response.status_code} - {response.text}")
  1305. except requests.exceptions.RequestException as e:
  1306. raise Exception(f"Network error calling Ollama: {str(e)}")
  1307. except json.JSONDecodeError as e:
  1308. raise Exception(f"Error parsing Ollama response: {str(e)}")
  1309. except Exception as e:
  1310. raise Exception(f"Error calling local Llama: {str(e)}")
  1311. # %%
  1312. # %%
  1313. import openai
  1314. # Your data
  1315. data_json = '{"study": "example study data"}'
  1316. prompt = f"Given the following study data, provide detailed information about the type of study and the brain area involved:\n\n{data_json}"
  1317. # Make the API request to OpenAI
  1318. response = openai.ChatCompletion.create(
  1319. model="gpt-4",
  1320. messages=[
  1321. {"role": "system", "content": "You are a helpful assistant."},
  1322. {"role": "user", "content": prompt}
  1323. ],
  1324. max_tokens=1500,
  1325. n=1,
  1326. stop=None,
  1327. temperature=0.5,
  1328. )
  1329. # Print the response
  1330. print(response['choices'][0]['message']['content'].strip())

03_LLM_processor.ipynb at commit 2e829ad, under MIT · at the source

Overview

Authors: Richard J Young1, Jorge Fonseca2, Brach Poston1,3
  1. Interdisciplinary Neuroscience, University of Nevada, Las Vegas, NV 89154, USA
  2. Department of Computer Science, Howard R. Hughes College of Engineering, University of Nevada, Las Vegas, NV 89154, USA
  3. Department of Kinesiology and Nutrition Sciences, University of Nevada, Las Vegas, NV 89154, USA
Institutions: University of Nevada, Las Vegas (United States)
Journal: Bioengineering (Basel, Switzerland), volume 13, issue 7, article 773
Dates: received 27 May 2026; accepted 28 June 2026; published online 2 July 2026
Type: Research article · Language: English
License: CC BY
Identifiers: DOI 10.3390/bioengineering13070773 · PMID 42510439 · PMCID PMC13404533 · OpenAlex W7167097198
Open access: gold, a free copy (OpenAlex)
Status: code verified
Categories: other (modality), human (organism), Parkinson's (population), clinical / translational (subfield)
Methods: Connectivity, Statistics, Machine learning
Keywords: clinical trial data extraction, large language models, neuromodulation, transcranial direct current stimulation, API integration, systematic review methodology, Parkinson’s disease, evidence synthesis, research automation
Topic: Machine Learning in Healthcare (Artificial Intelligence, Computer Science), according to OpenAlex
Citations: not cited yet (Europe PMC); 115 references in the paper

Abstract

(1) Background: Clinical trial data extraction from registries such as ClinicalTrials.gov remains labor-intensive and error-prone, often missing critical details hidden in unstructured protocol descriptions. Large Language Models (LLMs) offer potential to automate this process, yet systematic multi-model comparisons on real clinical trial data remain scarce. (2) Methods: Four LLMs (OpenAI o4-mini-high, Anthropic Claude-Sonnet-4, Google Gemini 2.5-Pro, and Meta Llama-4-Maverick) extracted brain stimulation parameters from 67 transcranial direct current stimulation (tDCS) trials in Parkinson’s disease via a structured JSON schema. Pairwise inter-model agreement was quantified with Cohen’s Kappa and percentage agreement across binary, categorical, and multi-component task tiers. (3) Results: Under exact-string matching, agreement was near-perfect for binary classifications (non-invasive classification: 100%; brain stimulation presence: 99.3%, κ = 0.50) and substantial for categorical extractions (primary stimulation type: 96.4%, κ = 0.70), but fell to 48.6% (κ = 0.43) for complex anatomical targets. Numeric parameters revealed model-specific strengths: o4-mini-high and Claude-Sonnet-4 achieved perfect duration agreement (r = 1.000, n = 19) while Llama-4-Maverick diverged substantially (r < 0.12). Validation against an expert gold standard (100% inter-annotator agreement on a 20-trial overlap) confirmed high extraction accuracy across all features (mean 93.7–98.9%). Crucially, the low agreement on anatomical targets proved to be an artifact of exact-string scoring: under the same semantic matching used to measure accuracy, inter-model agreement rose to 97.0%, coinciding with the 95.5% expert accuracy. Inter-model agreement therefore tracks accuracy once both are measured on a common basis. (4) Conclusions: Exact-string inter-model agreement decreases with task complexity, but this decline largely reflects interchangeable free-text wording rather than reduced accuracy. Evaluated semantically, agreement and expert accuracy are both high and closely aligned. A residual risk is not low accuracy but the rare error shared across all models, which agreement cannot detect, and which overall accuracy can itself mask when one class dominates. These findings inform hybrid human–AI systematic review pipelines in which targeted expert oversight focuses on shared-error and minority-class detection.

Reproduced under the paper's license (CC BY), from the paper cited above.

Repository

Its files are read in the Code ↔ Paper reader above, with 14 matches between paragraphs and lines of code.

ricyoung/ClinicalTrialLLM-Extractor

License: MIT
State: the link answers, verified on 27 September 2026
Evidence: files inventoried
Commit: 2e829ad114ef2a1ea3f8e48732d8ab6fe5fc5138, 1 July 2026
Languages: Jupyter (9), Python (1)
Size: 36 files, 10 scripts
Software Heritage: not archived
Found in: “Data Availability Statement”
Holds: README, license file, environment (requirements.txt), 9 notebooks
Not found: CITATION.cff, tests, continuous integration, documentation
Tools: NumPy (9 files), pandas (9 files), Matplotlib (6 files), scikit-learn (6 files), Pingouin (2 files)
Availability: 1 check, the latest on 27 September 2026: the link answers
  • 27 September 2026: the link answers
12 files

The paper's code and data availability statement is in the Data section.

Tracing map

Proposed by the machine: these links were found in the paper and verified at the source, without human review. The map will receive a Zenodo DOI once one of the paper's authors has validated it with their ORCID.

What the map holds:

  • 1 repository of the authors' code, each at its verified commit, with its license and how the link was found in the paper;
  • 10 scripts, each with its path and the digest of its content;
  • 14 matches between paragraphs of the paper and lines of the code (method lexical-v1);
  • neither the text of the paper nor the code itself.

Its JSON (tracing-map.json) is deposited on Zenodo with its DOI once the map is validated.

Data

No dataset and no data link were found in the paper.

Data Availability Statement

All clinical trial data are publicly available from ClinicalTrials.gov. Processed datasets, analysis code, and the complete extraction pipeline are available at: https://github.com/ricyoung/ClinicalTrialLLM-Extractor (accessed on 27 June 2026) (Appendix C). The normalization lexicon, gold-standard annotations, and annotation codebook are also provided as Supplementary Materials (Files S1–S4).

Reproduced under the paper's license (CC BY), from the paper cited above.

Versions

The history of this record: each version stored by the harvester or made by a correction of its authors or of the maintainers of its code, and what changed in its facts. The texts of the paper (its abstract, its availability statements) are not part of it; versions that changed only those are not listed.

Version 1, 27 September 2026: the first record

Recorded: type, language, journal, volume, issue, pages, dates, 3 authors, 9 keywords, 112 references.

Cite

This paper

Young, R. J., Fonseca, J., & Poston, B. (2026). From API to Action: A Multi-Model Comparison of OpenAI, Anthropic, Google, and Meta LLMs for Clinical Trial Data Extraction. Bioengineering (Basel, Switzerland), 13(7), 773. https://doi.org/10.3390/bioengineering13070773

BibTeX

@article{young2026api,
author = {Young, Richard J and Fonseca, Jorge and Poston, Brach},
title = {{From API to Action: A Multi-Model Comparison of OpenAI, Anthropic, Google, and Meta LLMs for Clinical Trial Data Extraction}},
journal = {Bioengineering (Basel, Switzerland)},
year = {2026},
month = jul,
volume = {13},
number = {7},
pages = {773},
publisher = {Multidisciplinary Digital Publishing Institute (MDPI)},
issn = {2306-5354},
doi = {10.3390/bioengineering13070773},
url = {https://doi.org/10.3390/bioengineering13070773},
pmid = {42510439},
pmcid = {PMC13404533}
}

RIS

TY - JOUR
AU - Young, Richard J
AU - Fonseca, Jorge
AU - Poston, Brach
TI - From API to Action: A Multi-Model Comparison of OpenAI, Anthropic, Google, and Meta LLMs for Clinical Trial Data Extraction
T2 - Bioengineering (Basel, Switzerland)
J2 - Bioengineering (Basel)
PY - 2026
DA - 2026/07/02
VL - 13
IS - 7
SP - 773
SN - 2306-5354
PB - Multidisciplinary Digital Publishing Institute (MDPI)
DO - 10.3390/bioengineering13070773
UR - https://doi.org/10.3390/bioengineering13070773
LA - en
ER -

CSL-JSON

{
"id": "10.3390/bioengineering13070773",
"type": "article-journal",
"title": "From API to Action: A Multi-Model Comparison of OpenAI, Anthropic, Google, and Meta LLMs for Clinical Trial Data Extraction",
"container-title": "Bioengineering (Basel, Switzerland)",
"author": [
{
"family": "Young",
"given": "Richard J"
},
{
"family": "Fonseca",
"given": "Jorge"
},
{
"family": "Poston",
"given": "Brach"
}
],
"container-title-short": "Bioengineering (Basel)",
"volume": "13",
"issue": "7",
"page": "773",
"DOI": "10.3390/bioengineering13070773",
"PMID": "42510439",
"PMCID": "PMC13404533",
"ISSN": "2306-5354",
"publisher": "Multidisciplinary Digital Publishing Institute (MDPI)",
"URL": "https://doi.org/10.3390/bioengineering13070773",
"language": "en",
"issued": {
"date-parts": [
[
2026,
7,
2
]
]
}
}

The tracing map gets a citation of its own once an author has validated it and it has a DOI.

Similar papers

The papers with a page that share the most with this one: the tools found in their code, their categories, datasets, cited references and authors, the rarest counting most.

[1] doi:10.1162/nol.a.244 [code]
A Novel Approach to Map the Causal Impact of Brain Stimulation on Semantic Processing With Language Models.
Journal: Neurobiology of language (Cambridge, Mass.)
In common: Pingouin, scikit-learn, Matplotlib, 1 other tool, other, 1 reference
[2] doi:10.1038/s41531-026-01380-1 [code]
Identifying maximal beta power from directional subthalamic local field potentials in Parkinson's disease.
Journal: NPJ Parkinson's disease
In common: Pingouin, scikit-learn, pandas, 2 other tools, Parkinson's, clinical / translational
[3] doi:10.1002/alz.71649 [code]
Postmortem brain MRI reveals differential associations of subcortical and limbic volumes with cortical thinning and neurodegenerative pathologies.
Journal: Alzheimer's & dementia : the journal of the Alzheimer's Association
In common: Pingouin, scikit-learn, pandas, 2 other tools, Parkinson's
[4] doi:10.1093/braincomms/fcag218 [code]
Magnetoencephalography biomarkers for assessing myelin content and neuronal function in acute optic neuritis.
Journal: Brain communications
In common: Pingouin, scikit-learn, pandas, 2 other tools, other
[5] doi:10.1016/j.nicl.2026.104012 [code]
Structural-functional multilayer brain network properties and outcome of combined repetitive transcranial magnetic stimulation and psychotherapy for obsessive-compulsive disorder.
Journal: NeuroImage. Clinical
In common: Pingouin, scikit-learn, pandas, 2 other tools, other
[6] doi:10.7554/elife.100605 [code]
Age-related changes in ‘cortical’ 1/f dynamics are linked to cardiac activity
Journal: n/a
In common: Pingouin, scikit-learn, pandas, 2 other tools, other
[7] doi:10.1038/s41398-026-04081-8 [code]
Functional system-specific brain aging across the Alzheimer's disease continuum.
Journal: Translational psychiatry
In common: Pingouin, scikit-learn, pandas, 2 other tools, clinical / translational
[8] doi:10.1016/j.patter.2026.101538 [code]
A multi-modal foundation model for brain disease diagnosis and medical imaging.
Journal: Patterns (New York, N.Y.)
In common: scikit-learn, pandas, Matplotlib, 1 other tool, clinical / translational, 1 reference
[9] doi:10.1162/imag.a.1259 [code]
Neuronal avalanches as a predictive biomarker for guiding tailored BCI training programs.
Journal: Imaging neuroscience (Cambridge, Mass.)
In common: Pingouin, scikit-learn, pandas, 2 other tools, clinical / translational
[10] doi:10.1371/journal.pbio.3003856 [code]
Aging and metabolism contribute separately to brain-body health.
Journal: PLoS biology
In common: scikit-learn, pandas, Matplotlib, 1 other tool, clinical / translational, 1 reference

Contribute

The authors of this paper can claim it, correct its record and validate its tracing map, and the maintainers of its code (its owner, or a public member of its organization) correct what it says of their repository; anyone signed in can ask for its removal. Every request goes to OSCR's own machine, which answers it; your account page follows them.

Sign in with ORCID to claim this paper as one of its authors, correct its record or validate its tracing map: when the paper's metadata lists your ORCID iD, you are recognized at once. Maintainers of its code: sign in with GitHub, then claim the repository on your account page.

Request its removal

To ask OSCR to remove this record, the copies of its authors' scripts or its tracing map, use the removal request page: signed in, you say who you are, what to remove and why, then review and confirm the request. Published rules decide every request (how).

Discussion, reproductions, activity

Discussion: questions and error reports about this paper and its code, from signed-in readers and its authors. It opens with sign-in.

Reproductions: reports from readers who ran the authors' code: what they reproduced, with which environment, commit and data. It opens with sign-in.

Activity: what happens around this paper: new versions of its record, its map's validation, discussions and reproductions. It opens with sign-in.